전체 셸 스크립트를 실행하기 전에 읽는 방법은 무엇입니까?


35

일반적으로 스크래치를 편집하면 실행중인 모든 스크립트 사용에 오류가 발생하기 쉽습니다.

내가 이해하는 한, bash (다른 쉘도?)는 스크립트를 점진적으로 읽으므로 스크립트 파일을 외부에서 수정하면 잘못된 내용을 읽기 시작합니다. 그것을 막을 방법이 있습니까?

예:

sleep 20

echo test

이 스크립트를 실행하면 bash는 첫 번째 줄 (예 : 10 바이트)을 읽고 절전 모드로 전환합니다. 다시 시작하면 10 번째 바이트부터 시작하는 스크립트의 내용이 다를 수 있습니다. 새 스크립트에서 줄 중간에있을 수 있습니다. 따라서 실행중인 스크립트가 손상됩니다.


"스크립트를 외부에서 수정"한다는 것은 무엇을 의미합니까?
maulinglawns

1
어쩌면 모든 내용을 함수 등으로 감싸는 방법이있을 수 있으므로 쉘이 전체 스크립트를 먼저 읽습니다. 그러나 함수를 호출하는 마지막 줄은 EOF까지 읽을 수 있습니까? 어쩌면 마지막을 생략하면 \n트릭을 할 수 있습니까? 아마도 서브 쉘 ()이할까요? 나는 그것에 익숙하지 않습니다, 도와주세요!
VasyaNovikov

스크립트에 같은 내용이 sleep 20 ;\n echo test ;\n sleep 20있고 편집을 시작 하면 @maulinglawns 가 잘못 작동 할 수 있습니다. 예를 들어, bash는 스크립트의 처음 10 바이트를 읽고 sleep명령을 이해하고 절전 모드로 전환 할 수 있습니다. 다시 시작하면 10 바이트부터 시작하는 파일의 내용이 달라집니다.
VasyaNovikov

1
그래서 당신이 말하는 것은 실행중인 스크립트를 편집하고 있다는 것입니까? 먼저 스크립트를 중지하고 편집 한 다음 다시 시작하십시오.
maulinglawns

@ maulinglawns 네, 기본적으로 그렇습니다. 문제는 스크립트를 중지하는 것이 편리하지 않으며 항상 그렇게하는 것을 기억하기가 어렵다는 것입니다. bash가 전체 스크립트를 먼저 읽도록 강제하는 방법이 있습니까?
VasyaNovikov

답변:


43

예, 쉘 bash은 특히 ​​한 번에 한 줄씩 파일을 읽도록주의하므로 대화식으로 파일을 사용할 때와 동일하게 작동합니다.

파이프와 같은 파일을 찾을 수 없을 때 문자를 bash지나 읽지 않도록 한 번에 한 바이트 씩 읽습니다 \n. 파일을 검색 할 수 있으면 한 번에 전체 블록을 읽음으로써 최적화되지만 \n.

즉, 다음과 같은 작업을 수행 할 수 있습니다.

bash << \EOF
read var
var's content
echo "$var"
EOF

또는 스스로 업데이트되는 스크립트를 작성하십시오. 그것이 당신에게 그 보증을주지 않으면 할 수 없을 것입니다.

지금, 당신이 그런 일을하고 싶어하는 경우는 거의 없으며, 알다시피, 그 기능이 유용한 것보다 더 자주 방해되는 경향이 있습니다.

이를 피하려면 파일을 제자리에서 수정하지 마십시오 (예 : 사본을 수정하고 사본을 제자리로 이동 (예 : sed -i또는 perl -pi일부 편집기의 경우)).

또는 다음과 같이 스크립트를 작성할 수 있습니다.

{
  sleep 20
  echo test
}; exit

( ; exit와 같은 줄에 }있어야합니다. 닫는 바로 앞에 괄호 안에 넣을 수도 있습니다).

또는:

main() {
  sleep 20
  echo test
}
main "$@"; exit

쉘은 exit무언가를 시작하기 전에 스크립트를 읽어야합니다 . 그러면 셸이 스크립트에서 다시 읽히지 않습니다.

그것은 전체 스크립트가 메모리에 저장됨을 의미합니다.

스크립트 구문 분석에도 영향을 줄 수 있습니다.

예를 들어,에 bash:

export LC_ALL=fr_FR.UTF-8
echo $'St\ue9phane'

UTF-8로 인코딩 된 U + 00E9를 출력합니다. 그러나 다음과 같이 변경하면

{
  export LC_ALL=fr_FR.UTF-8
  echo $'St\ue9phane'
}

\ue9명령은이 경우에 해석되는 당시에 유효했던 캐릭터 세트로 확장 될 전에export 명령이 실행된다.

또한 일부 쉘 에서 sourceaka .명령을 사용하면 소스 파일에 대해 동일한 종류의 문제가 발생합니다.

그건 그렇지 않다의 bash그 것처럼 source명령을 해석하기 전에 완전히 파일을 읽습니다. bash구체적으로 작성하는 경우 스크립트 시작 부분에 추가하여 실제로 사용할 수 있습니다.

if [[ ! $already_sourced ]]; then
  already_sourced=1
  source "$0"; exit
fi

(나는 당신이 미래의 버전 bash이 현재 제한으로 볼 수있는 행동을 바꿀 수 있다고 상상할 수 있지만 (bash와 AT & T ksh는 말할 수있는 한 POSIX와 같은 유일한 쉘입니다) 그리고 already_sourced이 변수가)이 BASH_SOURCE 변수의 내용에 영향을 미칠 것을 언급하지 않기에, 환경에없는 것을 가정으로 트릭은 비트 취성


@ VasyaNovikov, 현재 SE에 문제가있는 것 같습니다 (적어도 나에게는). 내가 추가했을 때 몇 가지 답변 만 있었고, 16 분 전에 게시되었다고 말했지만 (또는 아마도 내 구슬을 잃어 버린 것일 수도 있음) 귀하의 의견은 지금 만 나타났습니다. 어쨌든, 파일 크기가 커질 때 문제를 피하기 위해 여기에 필요한 여분의 "종료"를 기록하십시오 (응답에 추가 한 주석에 언급 된 바와 같이).
Stéphane Chazelas

스테판, 다른 해결책을 찾았습니다. 사용하는 것 }; exec true입니다. 이런 식으로 파일 끝에 줄 바꿈이 필요하지 않으므로 일부 편집자 (예 : emacs)에게 친숙합니다. 내가 제대로 작동한다고 생각할 수있는 모든 테스트}; exec true
VasyaNovikov

@VasyaNovikov, 무슨 뜻인지 잘 모르겠습니다. 보다 나은 방법은 }; exit무엇입니까? 종료 상태도 잃고 있습니다.
Stéphane Chazelas

다른 질문에서 언급했듯이 : 먼저 전체 파일을 구문 분석 한 다음 dot 명령 ( . script)을 사용하는 경우 복합 문을 실행하는 것이 일반적입니다.
schily

@schily, 그렇습니다.이 답변에서 AT & T ksh 및 bash의 한계로 언급했습니다. 다른 POSIX 유형 쉘에는 이러한 제한이 없습니다.
Stéphane Chazelas 2016 년

12

파일을 삭제하면됩니다 (예 : 복사, 삭제, 사본 이름을 원래 이름으로 다시 변경). 실제로 많은 편집자가이 작업을 수행하도록 구성 할 수 있습니다. 파일을 편집하고 변경된 버퍼를 저장하면 파일을 덮어 쓰는 대신 이전 파일의 이름을 바꾸고 새 파일을 만든 다음 새 내용을 새 파일에 넣습니다. 따라서 실행중인 스크립트는 문제없이 계속되어야합니다.

vim 및 emacs에서 쉽게 사용할 수있는 RCS와 같은 간단한 버전 제어 시스템을 사용하면 변경 히스토리가 있다는 이점을 얻을 수 있으며 체크 아웃 시스템은 기본적으로 현재 파일을 제거하고 올바른 모드로 다시 작성해야합니다. (물론 그러한 파일을 하드 링크하는 것을 조심하십시오).


"삭제"는 실제로 프로세스의 일부가 아닙니다. 적절하게 원자로 만들려면 대상 파일의 이름을 바꾸십시오. 삭제 단계가있는 경우 삭제 이후에 이름을 바꾸기 전에 프로세스가 죽을 위험이 있습니다. 또는 독자는 해당 창에서 파일에 액세스하려고 시도하고 사용 가능한 이전 버전이나 새 버전을 찾지 않습니다.
Charles Duffy

11

가장 간단한 해결책 :

{
  ... your code ...

  exit
}

이런 식으로 bash는 {}블록을 실행하기 전에 전체 블록 을 읽고 exit지시문은 코드 블록 외부에서 아무것도 읽지 않도록합니다.

스크립트를 "실행"하지 않고 "소스"하려는 경우 다른 솔루션이 필요합니다. 그러면 작동합니다.

{
  ... your code ...

  return 2>/dev/null || exit
}

또는 종료 코드를 직접 제어하려는 경우 :

{
  ... your code ...

  ret="$?";return "$ret" 2>/dev/null || exit "$ret"
}

oil! 이 스크립트는 편집, 소싱 및 실행이 안전합니다. 처음 읽을 때 밀리 초 단위로 수정하지 않아야합니다.


1
내가 찾은 것은 EOF가 보이지 않고 파일 읽기를 중단하지만 "버퍼링 된 스트림"처리 과정에서 엉켜서 파일 끝을 지나서 찾는 것입니다. 파일은 크게 증가하지 않지만 파일을 이전보다 두 배 이상 크게 만들면 나빠 보입니다. 나는 bash 관리자에게 버그를 곧보고 할 것이다.
Stéphane Chazelas


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
terdon

5

개념의 증거. 다음은 스스로 수정하는 스크립트입니다.

cat <<EOF >/tmp/scr
#!/bin/bash
sed  s/[k]ept/changed/  /tmp/scr > /tmp/scr2

# this next line overwites the on disk copy of the script
cat /tmp/scr2 > /tmp/scr
# this line ends up changed.
echo script content kept
EOF
chmod u+x /tmp/scr
/tmp/scr

우리는 변경된 버전 인쇄를 본다

bash로드는 파일 핸들을 스크립트에 열어두기 때문에 파일의 변경 사항이 즉시 나타납니다.

메모리 내 사본을 업데이트하지 않으려면 원본 파일을 연결 해제하고 교체하십시오.

이를 수행하는 한 가지 방법은 sed -i를 사용하는 것입니다.

sed -i '' filename

개념의 증거

cat <<EOF >/tmp/scr
#!/bin/bash
sed  s/[k]ept/changed/  /tmp/scr > /tmp/scr2

# this next line unlinks the original and creates a new copy.
sed -i ''  /tmp/scr

# now overwriting it has no immediate effect
cat /tmp/scr2 > /tmp/scr
echo script content kept
EOF

chmod u+x /tmp/scr
/tmp/scr

편집기를 사용하여 스크립트를 변경하는 경우 "백업 복사본 유지"기능을 활성화하면 편집기가 기존 버전을 덮어 쓰지 않고 변경된 버전을 새 파일에 쓰도록 할 수 있습니다.


2
아니요, bash파일을 열지 않습니다 mmap(). 대화식 일 때 터미널 장치에서 명령을받을 때처럼 필요에 따라 한 번에 한 줄씩 읽는 것이 중요합니다.
Stéphane Chazelas

2

스크립트를 블록 {}으로 묶는 것이 가장 좋은 방법이지만 스크립트를 변경해야합니다.

F=$(mktemp) && cp test.sh $F && bash $F; rm $F;

두 번째로 가장 좋은 옵션 ( tmpfs 가정 )은 스크립트에서 사용하면 $ 0를 나누는 단점이 있습니다.

같은 F=test.sh; tail -n $(cat "$F" | wc -l) "$F" | bash파일을 사용하는 것은 전체 파일을 메모리에 유지하고 $ 0를 나누기 때문에 덜 이상적입니다.

마지막 수정 시간, 읽기 잠금 및 하드 링크가 방해받지 않도록 원본 파일을 건드리지 마십시오. 이렇게하면 파일을 실행하는 동안 편집기를 열어 둘 수 있으며 rsync는 파일의 백업 및 하드 링크 기능을 불필요하게 체크섬 할 필요가 없습니다.

편집시 파일을 바꾸는 것은 효과가 있지만 다른 스크립트 / 사용자에게 강제 할 수 없으므로 잊어 버릴 수 있기 때문에 덜 강력합니다. 그리고 다시 그것은 하드 링크를 끊을 것입니다.


사본을 만드는 모든 것이 작동합니다. tac test.sh | tac | bash
Jasen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.