두 대의 컴퓨터에서 실행할 수있는 스크립트가 있습니다. 이 두 머신은 동일한 자식 저장소에서 스크립트 사본을 가져옵니다. 스크립트는 올바른 인터프리터 (예 :)로 실행해야합니다 zsh
.
불행하게도, 모두 env
와 zsh
로컬 및 원격 시스템에서 서로 다른 위치에 살고 :
원격 기계
$ which env
/bin/env
$ which zsh
/some/long/path/to/the/right/zsh
로컬 머신
$ which env
/usr/bin/env
$which zsh
/usr/local/bin/zsh
스크립트를 실행할 때 /path/to/script.sh
항상 사용 Zsh
가능한 것을 사용 하도록 shebang을 어떻게 설정할 수 PATH
있습니까?
답변
shebang은 순전히 정적이므로 shebang을 통해 직접 해결할 수 없습니다. 이 LCM이 zsh가 아닌 경우 shebang에 “최소한 공통 멀티 플라이어”(쉘 관점에서)를 가지고 올바른 쉘로 스크립트를 다시 실행하는 것이 가능합니다. 다시 말해서 : 모든 시스템에서 발견 된 쉘에 의해 스크립트가 실행되도록하고, zsh
전용 기능을 테스트하고 테스트가 거짓으로 판명 되면 테스트가 성공하고 계속 진행할 스크립트 exec
를 zsh
로 설정하십시오.
zsh
예를 들어의 고유 한 기능 중 하나 는 $ZSH_VERSION
변수 의 존재입니다 .
#!/bin/sh -
[ -z "$ZSH_VERSION" ] && exec zsh - "$0" ${1+"$@"}
# zsh-specific stuff following here
echo "$ZSH_VERSION"
이 간단한 경우, 스크립트는 먼저 실행됩니다 /bin/sh
(80 년대 유닉스 계열 시스템 은 Bourne 또는 POSIX를 이해 #!
하고 /bin/sh
있지만 구문은 둘 다 호환됩니다). 경우 $ZSH_VERSION
입니다 하지 스크립트 exec
자체를 통해 수행 zsh
됩니다. $ZSH_VERSION
이 설정된 경우 (대체로 스크립트가 이미 실행 됨 zsh
) 테스트는 건너 뜁니다. Voilà.
이 경우에만 실패 zsh
전혀없는$PATH
합니다.
편집 : 확인하려면, 당신 만exec
zsh
평소 A 를 사용하려면 다음과 같은 것을 사용할 수 있습니다
for sh in /bin/zsh \
/usr/bin/zsh \
/usr/local/bin/zsh; do
[ -x "$sh" ] && exec "$sh" - "$0" ${1+"$@"}
done
이것은 당신이 의도 하지 않은 exec
무언가 를 실수로 ‘들어가는’것을 막을 수 있습니다 .$PATH
zsh
답변
몇 년 동안 필자는 스크립트를 실행해야하는 시스템에서 다양한 Bash 위치를 처리하기 위해 비슷한 것을 사용했습니다.
배쉬 / Zsh / 등
#!/bin/sh
# Determines which OS and then reruns this script with approp. shell interp.
LIN_BASH="/bin/sh";
SOL_BASH="/packages/utilities/bin/sun5/bash";
OS_TYPE=`uname -s`;
if [ $OS_TYPE = "SunOS" ]; then
$SOL_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
elif [ $OS_TYPE = "Linux" ]; then
$LIN_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
else
echo "UNKNOWN OS_TYPE, $OS_TYPE";
exit 1;
fi
exit 0;
### BEGIN
...script goes here...
위의 내용은 다양한 통역사에 쉽게 적용 할 수 있습니다. 핵심은이 스크립트가 처음에 Bourne 쉘로 실행된다는 것입니다. 그런 다음 재귀 적으로 자신을 두 번 호출하지만 주석을 ### BEGIN
사용하여 모든 것을 구문 분석합니다.sed
합니다.
펄
Perl에 대한 유사한 트릭이 있습니다.
#!/bin/sh
LIN_PERL="/usr/bin/perl";
SOL_PERL="/packages/perl/bin/perl";
OS_TYPE=`uname -s`;
if [ $OS_TYPE = "SunOS" ]; then
eval 'exec $SOL_PERL -x -S $0 ${1+"$@"}';
elif [ $OS_TYPE = "Linux" ]; then
eval 'exec $LIN_PERL -x -S $0 ${1+"$@"}';
else
echo "$OS_TYPE: UNSUPORRTED OS/PLATFORM";
exit 0;
fi
exit 0;
#!perl
...perl script goes here...
이 메소드는 파일을 실행할 때 Perl의 기능을 사용하여 해당 파일을 구문 분석하여 line 앞에있는 모든 행을 건너 뜁니다 #! perl
.
답변
참고 : @ jw013은 아래 주석에서 다음과 같은 지원되지 않는 이의 제기를합니다.
공감대는 자체 수정 코드가 일반적으로 나쁜 습관으로 간주되기 때문입니다. 예전에는 소규모 조립 프로그램으로 돌아가서 조건부 분기를 줄이고 성능을 향상시키는 영리한 방법 이었지만 오늘날 보안 위험이 장점보다 중요합니다. 스크립트를 실행 한 사용자에게 스크립트에 대한 쓰기 권한이 없으면 접근 방식이 작동하지 않습니다.
내가 지적으로 자신의 보안 반대 대답 어떤 특별한 권한이 있습니다 만되면 필요 에 따라 설치 / 업데이트를 위해 행동 설치 / 업데이트 자동 설치 개인적으로 확보 꽤 부를 것이다 – 스크립트. 나는 또한 man sh
비슷한 방법으로 비슷한 목표를 달성 할 수 있다고 언급했다. 당시에는 답변에 표시되거나 표시되지 않을 수있는 보안 결함이나 일반적으로 조언 되지 않은 방법이 무엇이든 질문에 대한 답변보다 질문에 기반을 둔 것으로 나타났습니다.
스크립트를 /path/to/script.sh로 실행하면 항상 PATH에서 사용 가능한 Zsh를 사용하도록 shebang을 어떻게 설정할 수 있습니까?
만족스럽지 못한 @ jw013은 적어도 몇 가지 잘못된 진술 로 아직지지받지 못한 논증을 진전시켜 이의 제기를 계속했습니다 .
두 개의 파일이 아닌 단일 파일을 사용합니다. [
man sh
참조]
패키지는 하나 개의 파일이 다른 파일을 수정 있습니다. 파일 자체가 수정되었습니다. 이 두 경우에는 뚜렷한 차이가 있습니다. 입력을 받고 출력을 생성하는 파일은 괜찮습니다. 실행될 때 자체적으로 변경되는 실행 파일은 일반적으로 나쁜 생각입니다. 당신이 지적한 예는 그렇게하지 않습니다.
우선 :
유일한 EXECUTABLE ANY IN CODE EXECUTABLE SHELL SCRIPT 인#!
자체
(하지만 심지어 #!
입니다 공식적으로 지정되지 않은 )
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
쉘 스크립트는 텍스트 파일 일뿐입니다. 다른 실행 파일 로 읽어야 하는 명령이 있으면 다른 실행 파일에서 해당 명령을 해석 한 다음 마지막으로 다른 실행 파일에서 해당 해석 을 실행 합니다. 쉘 스크립트. 쉘 스크립트 파일을 실행하는 데 두 개 미만의 파일이 포함될 수 는 없습니다 .zsh
자체 컴파일러에는 예외가있을 수 있지만이 경우 경험이 거의 없으며 여기에 표시되지 않습니다.
쉘 스크립트의 해시 뱅 은 의도 한 인터프리터 를 가리켜 야합니다 거나 관련이없는 것으로 폐기 .
쉘의 토큰 인식 / 실행 동작은 표준 정의
쉘은 입력을 해석하고 해석하는 두 가지 기본 모드를 가지고 있습니다 : 현재 입력이 a를 정의 <<here_document
하거나 정의 { ( command |&&|| list ) ; } &
중입니다. 즉, 쉘은 토큰을 해석합니다. 을 읽은 후에 실행해야하는 명령의 구분자로 을 합니다. 또는 파일을 작성하고 다른 명령의 파일 디스크립터에 맵핑하는 지시 사항으로 사용하십시오. 그게 다야.
쉘을 실행하는 명령을 해석 할 때 예약어 세트에서 토큰을 구분합니다 . 쉘이 시작 토큰을 발견하면 목록이 개행과 같은 닫는 토큰 (해당되는 경우) 또는 다음과 같은 닫는 토큰으로 구분 될 때까지 명령 목록을 계속 읽어야 })
합니다.({
실행 전합니다.
쉘은 간단한 명령 과 복합 명령을 구분합니다. 화합물 명령 실행 전에 판독해야 명령의 집합이지만, 쉘은 수행하지 않고 $expansion
그 성분 중 하나에 간단한 명령 은 각각 단독으로 실행까지.
따라서 다음 예에서 ;semicolon
예약어는 개별 단순 명령을 구분 하지만 이스케이프되지 않은 \newline
문자는 두 복합 명령을 구분 합니다.
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
그것은 지침의 단순화입니다. 쉘 내장, 서브 쉘, 현재 환경 등 을 고려하면 훨씬 복잡해 지지만 여기서는 충분합니다.
내장 및 명령 목록에 대해 말하면 a function() { declaration ; }
는 단순한 명령 에 복합 명령 을 지정하는 수단 일뿐 입니다. 쉘은 $expansions
선언문 자체를 포함 <<redirections>
하지 말고 정의를 단일 리터럴 문자열로 저장하고 호출 할 때 내장 된 특수 쉘로 실행해야합니다.
따라서 실행 가능한 셸 스크립트로 선언 된 셸 함수는 해석되는 셸 메모리에 리터럴 문자열 형식으로 저장됩니다. 여기에 추가 된 문서를 입력으로 포함하도록 확장되지 않으며 셸 빌드로 호출 될 때마다 소스 파일과 독립적으로 실행됩니다. 쉘의 현재 환경이 지속되는 한.
A는 <<HERE-DOCUMENT
인라인 파일
경로 재 지정 연산자
<<
와<<-
둘 다 여기에서 문서 로 알려진 쉘 입력 파일에 포함 된 행을 명령의 입력으로 경로 재 지정할 수 있습니다.이 문서 는 다음 단어 이후에 시작 하여 사이에 s 가없는 구분 기호 와 a
\newline
만 포함 된 줄이있을 때까지 계속되는 단일 단어로 취급됩니다 . 그런 다음 다음 문서 가 있으면 시작합니다. 형식은 다음과 같습니다.\newline
[:blank:]
[n]<<word
here-document
delimiter
… 옵션
n
은 파일 디스크립터 번호를 나타냅니다. 숫자를 생략하면 여기 문서 는 표준 입력 (파일 설명자 0)을 나타냅니다.
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
알 겠어? 쉘 위의 모든 쉘에 대해 파일을 작성하고 파일 디스크립터에 맵핑합니다. 에서 zsh, (ba)sh
셸에서 일반 파일을 생성 /tmp
, 출력을 덤프, 기술자에 매핑 한 다음 삭제 /tmp
기술자의 커널의 사본이 모두 남아 있음을하므로 파일을. dash
모든 넌센스를 피하고 출력 처리를 |pipe
리디렉션 <<
대상을 대상 으로하는 익명 파일에 드롭합니다 .
이것은 dash
:
cmd <<HEREDOC
$(cmd)
HEREDOC
의 기능적으로 동일합니다 bash
:
cmd <(cmd)
동안 dash
의 구현은 적어도 POSIXly 휴대용이다.
이는도하지 SEVERAL 파일을
그래서 내가 할 때 아래 답변에서 :
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
다음과 같은 일이 발생합니다.
-
먼저
cat
쉘이 만든 파일의 내용을 실행 파일FILE
로 만든./file
다음 실행하십시오. -
커널은 해석
#!
과 통화/usr/bin/sh
A를<read
파일 기술자 에 할당을./file
. -
sh
에서 시작 하고 끝나는 복합 명령 으로 구성된 문자열을 메모리에 매핑합니다 ._fn()
SCRIPT
-
경우
_fn
라고,sh
먼저 디스크립터에 정의 된 파일을 매핑 해석하고 있어야<<SCRIPT...SCRIPT
하기 전에 호출_fn
하기 때문에 내장 유틸리티 특수로SCRIPT
인_fn
의<input.
-
에 의한 문자열 출력
printf
과은command
에 기록되어있다_fn
‘의 아웃 – 표준>&1
– 현재 쉘의 리디렉션되는ARGV0
– 나$0
. -
cat
잘린 현재 쉘의 인수를 통해<&0
표준 입력 파일 디스크립터를 연결합니다 .SCRIPT
>
ARGV0
$0
-
그 이미 현재의 읽기 완료 화합물 명령 ,
sh exec
새롭게 재 – – 실행이야$0
인수를.
시간이에서 ./file
의 포함 된 지침이되도록 지정 될 때까지라고 exec
다시 D, sh
단일 그것을 읽고 복합 명령 은이를 실행으로하면서, 한 번에 ./file
자체가 전혀 아무것도하지 않습니다 행복하게 새로운 내용을 받아 들일 제외합니다. 실제로 작업중인 파일은/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
감사합니다.
따라서 @ jw013은 다음을 지정합니다.
입력을 받고 출력을 생성하는 파일은 괜찮습니다 …
…이 답변에 대한 그의 잘못된 비판을 피하면서 그는 실제로 여기에 사용 된 유일한 방법을 무의식적으로 용서하고 있습니다.
cat <new_file >old_file
대답
여기에있는 모든 대답은 훌륭하지만 그중 어느 것도 완전히 맞지 않습니다. 모두가 당신이 당신의 역동적이고 영구적으로 경로를 찾을 수 없다고 주장하는 것 같습니다 #!bang
. 경로 독립적 인 shebang을 설정하는 데모는 다음과 같습니다.
데모
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
산출
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
알 겠어? 스크립트 자체를 덮어 씁니다. 그리고 git
동기화 후에 한 번만 발생 합니다. 그 시점부터 #! bang 라인에 올바른 경로가 있습니다.
이제 거의 모든 것이 보풀입니다. 이를 안전하게하려면 다음이 필요합니다.
-
맨 위에 정의되고 맨 아래에 호출되는 함수입니다. 이렇게하면 필요한 모든 것을 메모리에 저장하고 파일을 쓰기 전에 전체 파일을 읽을 수 있습니다.
-
경로를 결정하는 몇 가지 방법.
command -v
꽤 좋습니다. -
Heredocs는 실제 파일이기 때문에 실제로 도움이됩니다. 그 동안 스크립트를 저장합니다. 문자열도 사용할 수 있지만 …
-
스크립트를 실행하는 명령 목록과 동일한 명령 목록에서 스크립트를 덮어 쓰는 명령에서 쉘이 읽도록해야합니다.
보기:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
exec
명령을 한 줄 아래 로만 이동했습니다 . 지금:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
스크립트가 다음 명령에서 읽을 수 없기 때문에 출력의 후반부를 얻지 못합니다. 여전히 유일하게 누락 된 명령이 마지막 명령 이었기 때문에 :
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
스크립트는 대부분 heredoc에 있었기 때문에 필요한 것으로 나타 났지만, 제대로 계획하지 않으면 파일 스트림을자를 수 있습니다.
답변
shebang을 수정하는 자체 수정 스크립트를 갖는 한 가지 방법이 있습니다. 이 코드는 실제 스크립트 앞에 추가해야합니다.
#!/bin/sh
# unpatched
PATH=`PATH=/bin:/usr/bin:$PATH getconf PATH`
if [ "`awk 'NR==2 {print $2;exit;}' $0`" = unpatched ]; then
[ -z "`PATH=\`getconf PATH\`:/usr/local/bin:/some/long/path/to/the/right:$PATH command -v zsh`" ] && { echo "zsh not found"; exit 1; }
cp -- "$0" "$0.org" || exit 1
mv -- "$0" "$0.old" || exit 1
(
echo "#!`PATH=\`getconf PATH\`:$PATH command -v zsh`"
sed -n '/^##/,$p' $0.old
) > $0 || exit
chmod +x $0
rm $0.old
sync
exit
fi
## Original script starts here
일부 의견 :
-
스크립트가있는 디렉토리에서 파일을 작성하고 삭제할 권한이있는 사람이 한 번 실행해야합니다.
-
널리 알려진 신념에도 불구하고
/bin/sh
POSIX 셸, 심지어 POSIX 호환 OS로 보장되지 않으므로 레거시 Bourne 쉘 구문 만 사용합니다 . -
“포니”zsh를 선택하지 않도록 PATH를 POSIX 호환 경로로 설정하고 가능한 zsh 위치 목록을 설정합니다.
-
어떤 이유로 자체 수정 스크립트를 사용하는 것이 바람직하지 않은 경우 하나는 스크립트 대신 두 개의 스크립트를 배포하는 것이 쉽지 않습니다.