실행 중에 스크립트를 편집하면 어떻게됩니까?


31

Linux에서 프로세스가 처리되는 방식에 대한 오해의 결과 일 수있는 일반적인 질문이 있습니다.

내 목적을 위해 '스크립트'를 현재 사용자에 대해 실행 권한이 활성화 된 텍스트 파일에 저장된 bash 코드 스 니펫으로 정의하려고합니다.

서로를 호출하는 일련의 스크립트가 있습니다. 간단히하기 위해 스크립트 A, B 및 C라고하겠습니다. 스크립트 A는 일련의 명령문을 수행 한 다음 일시 중지 한 다음 스크립트 B를 실행 한 다음 일시 중지 한 다음 스크립트 C를 실행합니다. 단계는 다음과 같습니다.

스크립트 A를 실행하십시오.

  1. 일련의 진술
  2. 중지
  3. 스크립트 B 실행
  4. 중지
  5. 스크립트 C 실행

경험상 스크립트 A를 처음 일시 중지 할 때까지 실행 한 다음 스크립트 B에서 편집하면 해당 편집 내용이 코드를 다시 시작할 때 코드 실행에 반영됩니다. 마찬가지로 스크립트 A가 여전히 일시 중지 된 상태에서 스크립트 C를 편집 한 다음 변경 사항을 저장 한 후 계속할 수 있도록 허용하면 해당 변경 사항이 코드 실행에 반영됩니다.

다음은 실제 질문입니다. 스크립트 A가 여전히 실행중인 동안 편집 할 수있는 방법이 있습니까? 아니면 실행이 시작되면 편집이 불가능합니까?


2
나는 그것이 껍질에 달려 있다고 생각할 것입니다. bash를 사용하고 있다고 말하지만. 쉘이 스크립트를 내부적으로로드하는 방식에 의존하는 것처럼 보입니다.
strugee

파일을 실행하는 대신 소스를 지정하면 동작이 변경 될 수도 있습니다.
strugee

1
bash는 실행하기 전에 전체 스크립트를 메모리로 읽습니다.
w4etwetewtwet

2
@ handuel, 그렇지 않습니다. 프롬프트에서 "exit"를 입력하여 입력 한 명령의 해석을 시작할 때까지 기다리지 않습니다.
Stéphane Chazelas

1
@StephaneChazelas 그렇습니다. 터미널에서 읽을 수는 없지만 스크립트를 실행하는 것과 다릅니다.
w4etwetewtwet

답변:


21

유닉스에서, 대부분의 편집자들은 편집 된 내용을 포함하는 새로운 임시 파일을 만들어 작업합니다. 편집 된 파일이 저장되면 원본 파일이 삭제되고 임시 파일 이름이 원래 이름으로 바뀝니다. (물론, 데이터 손실을 방지하기위한 다양한 보호 수단이 있습니다.) 예를 들어, 실제로는 "in-place"가 아닌 ( "in-place") 플래그로 사용 sed하거나 perl호출 할 때 사용되는 스타일 -i입니다. 그것은 "오래된 이름을 가진 새로운 장소"라고 불렸어야했다.

유닉스는 열린 파일이 "삭제"되어 같은 이름의 새로운 파일이 생성 되더라도 파일이 닫힐 때까지 열린 파일이 계속 존재하기 때문에 (적어도 로컬 파일 시스템의 경우) 확실하게 작동합니다. (파일을 "삭제"하는 유닉스 시스템 호출을 실제로 "unlink"라고하는 것은 우연이 아닙니다.) 일반적으로 쉘 인터프리터에 소스 파일이 열려 있고 위에서 설명한 방식으로 파일을 "편집"하면 쉘은 원본 파일이 여전히 열려 있기 때문에 변경 사항을 볼 수 없습니다.

[참고 : 모든 표준 기반 주석과 마찬가지로 위의 내용은 여러 가지 해석이 가능하며 NFS와 같은 다양한 사례가 있습니다. Pedants는 예외적으로 의견을 채울 수 있습니다.]

물론 파일을 직접 수정하는 것도 가능합니다. 파일의 데이터를 덮어 쓸 수는 있지만 다음 데이터를 모두 이동하지 않으면 삭제하거나 삽입 할 수 없으므로 다시 쓰기 작업이 많이 필요하기 때문에 편집 목적으로는 그다지 편리하지 않습니다. 또한 이러한 이동을 수행하는 동안 파일의 내용을 예측할 수 없으며 파일을 연 프로세스에 문제가 생길 수 있습니다. 이를 해결하려면 (예 : 데이터베이스 시스템과 같이) 정교한 수정 프로토콜과 분산 잠금 세트가 필요합니다. 일반적인 파일 편집 유틸리티의 범위를 벗어나는 것.

따라서 쉘에서 파일을 처리하는 동안 파일을 편집하려면 두 가지 옵션이 있습니다.

  1. 파일에 추가 할 수 있습니다. 항상 작동해야합니다.

  2. 정확히 같은 길이의 새 내용 으로 파일을 덮어 쓸 수 있습니다 . 쉘이 파일의 해당 부분을 이미 읽었는지 여부에 따라 작동하거나 작동하지 않을 수 있습니다. 대부분의 파일 I / O에는 읽기 버퍼가 포함되어 있기 때문에 모든 셸에서 실행하기 전에 전체 복합 명령을 읽으므로이를 피할 수 없을 것입니다. 확실하지 않을 것입니다.

Posix 표준의 문구에 대해서는 실제로 파일을 실행하는 동안 스크립트 파일에 추가해야 할 가능성이 있으므로 모든 Posix 호환 쉘에서 작동하지 않을 수 있습니다. 때로는 POS 호환 쉘도 있습니다. YMMV입니다. 그러나 내가 아는 한, bash와 안정적으로 작동합니다.

증거로, 여기에 bash에서 악명 높은 99 병의 맥주 프로그램을 "루프 프리 (loop-free)"구현 dd으로 덮어 쓰고 추가 하는 데 사용 합니다 (덮어 쓰기는 현재 실행중인 줄을 대체하기 때문에 아마도 안전합니다. 정확히 동일한 길이의 주석이있는 파일; 자체 수정 동작없이 최종 결과를 실행할 수 있도록했습니다.)

#!/bin/bash
if [[ $1 == reset ]]; then
  printf "%s\n%-16s#\n" '####' 'next ${1:-99}' |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^#### $0 | cut -f1 -d:) bs=1 2>/dev/null
  exit
fi

step() {
  s=s
  one=one
  case $beer in
    2) beer=1; unset s;;
    1) beer="No more"; one=it;;
    "No more") beer=99; return 1;;
    *) ((--beer));;
  esac
}
next() {
  step ${beer:=$(($1+1))}
  refrain |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^next\  $0 | cut -f1 -d:) bs=1 conv=notrunc 2>/dev/null
}
refrain() {
  printf "%-17s\n" "# $beer bottles"
  echo echo ${beer:-No more} bottle$s of beer on the wall, ${beer:-No more} bottle$s of beer.
  if step; then
    echo echo Take $one down, pass it around, $beer bottle$s of beer on the wall.
    echo echo
    echo next abcdefghijkl
  else
    echo echo Go to the store, buy some more, $beer bottle$s of beer on the wall.
  fi
}
####
next ${1:-99}   #

이 명령을 실행하면 "No more"로 시작한 다음 -1과 음수로 무한정 계속됩니다.
Daniel Hershcovich

내가 할 경우 export beer=100스크립트를 실행하기 전에, 그것은 예상대로 작동합니다.
Daniel Hershcovich

@DanielHershcovich : 그렇습니다. 내 부주의 한 테스트. 나는 그것을 고쳤다 고 생각한다. 이제 선택적 카운트 매개 변수를 사용합니다. 매개 변수가 캐시 된 사본과 일치하지 않으면 자동으로 재설정하는 것이 더 재미 있고 흥미로운 수정 사항입니다.
rici

18

bash 명령을 실행하기 직전에 명령을 읽는지 확인하기 위해 먼 길을갑니다.

예를 들어

cmd1
cmd2

쉘은 스크립트를 블록 단위로 읽으므로 두 명령을 모두 읽고 첫 번째 명령을 해석 한 다음 cmd1스크립트 의 끝을 다시 찾은 다음 스크립트를 다시 읽고 읽 cmd2습니다.

쉽게 확인할 수 있습니다.

$ cat a
echo foo | dd 2> /dev/null bs=1 seek=50 of=a
echo bar
$ bash a
foo

(그 strace결과를 보면, 몇 년 전에 같은 것을 시도했을 때보 다 더 멋진 일을하는 것처럼 보입니다 (데이터를 여러 번 읽은 다음 다시 검색하십시오 ...). 최신 버전에는 더 이상 적용되지 않습니다).

그러나 다음과 같이 스크립트를 작성하면

{
  cmd1
  cmd2
  exit
}

쉘은 닫기까지 읽고 }메모리에 저장하고 실행해야합니다. 로 인해 exit쉘은 스크립트에서 다시 읽지 않으므로 쉘이 해석하는 동안 안전하게 편집 할 수 있습니다.

또는 스크립트를 편집 할 때 새 스크립트 사본을 작성하십시오. 쉘은 원래 쉘을 삭제하거나 이름을 변경하더라도 계속 읽습니다.

그렇게하려면 이름 the-script을 바꾸고 the-script.old복사 the-script.old하여 the-script편집하십시오.


4

쉘이 버퍼링을 사용하여 파일을 읽을 수 있기 때문에 스크립트가 실행되는 동안 스크립트를 수정하는 안전한 방법은 실제로 없습니다. 또한 스크립트를 새 파일로 바꾸어 스크립트를 수정하면 일반적으로 셸은 특정 작업을 수행 한 후에 만 ​​새 파일을 읽습니다.

종종 실행하는 동안 스크립트가 변경되면 쉘은 구문 오류를보고합니다. 이는 쉘이 스크립트 파일을 닫았다가 다시 열 때 파일에 바이트 오프셋을 사용하여 리턴시 자체 위치를 변경하기 때문입니다.


4

스크립트에 트랩을 설정 한 다음 exec새 스크립트 내용 을 사용하여이 문제를 해결할 수 있습니다 . 그러나이 exec호출은 실행중인 프로세스에서 도달 한 위치가 아니라 스크래치에서 스크립트를 시작하므로 스크립트 B가 호출됩니다 (그러므로 계속).

#! /bin/bash

CMD="$0"
ARGS=("$@")

trap reexec 1

reexec() {
    exec "$CMD" "${ARGS[@]}"
}

while : ; do sleep 1 ; clear ; date ; done

화면에 날짜가 계속 표시됩니다. 그런 다음 스크립트를 편집하고로 변경할 dateecho "Date: $(date)"있습니다. 그것을 작성하면 실행중인 스크립트는 여전히 날짜를 표시합니다. trap캡처하도록 설정 한 신호를 보내면 스크립트는 exec명령 $CMD및 인수 인 (현재 실행중인 프로세스를 지정된 명령으로 바꿉니다) $@. 당신은 실행하여이 작업을 수행 할 수 있습니다 kill -1 PIDPID가 실행중인 스크립트의 PID입니다 - - 출력이 보여 변경 Date:전과 date명령 출력.

스크립트의 "상태"를 외부 파일 (예 : / tmp)에 저장하고 프로그램을 다시 실행할 때 "재개"할 위치를 알기 위해 내용을 읽을 수 있습니다. 그런 다음 추가 트랩 종료 (SIGINT / SIGQUIT / SIGKILL / SIGTERM)를 추가하여 해당 tmp 파일을 정리할 수 있으므로 "스크립트 A"를 중단 한 후 다시 시작하면 처음부터 시작됩니다. 상태 저장 버전은 다음과 같습니다.

#! /bin/bash

trap reexec 1
trap cleanup 2 3 9 15

CMD="$0"
ARGS=("$@")
statefile='/tmp/scriptA.state'
EXIT=1

reexec() { echo "Restarting..." ; exec "$CMD" "${ARGS[@]}"; }
cleanup() { rm -f $statefile; exit $EXIT; }
run_scriptB() { /path/to/scriptB; echo "scriptC" > $statefile; }
run_scriptC() { /path/to/scriptC; echo "stop" > $statefile;  }

while [ "$state" != "stop" ] ; do

    if [ -f "$statefile" ] ; then
        state="$(cat "$statefile")"
    else
        state='starting'
    fi

    case "$state" in
        starting)         
            run_scriptB
        ;;
        scriptC)
            run_scriptC
        ;;
    esac
done

EXIT=0
cleanup

스크립트 를 캡처 $0하고 $@시작할 때 exec대신 해당 변수를 사용하여 문제를 해결했습니다 .
Drav Sloan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.