$ 0에 항상 스크립트 경로가 포함됩니까?


11

상단의 주석 섹션에서 도움말 및 버전 정보를 인쇄 할 수 있도록 현재 스크립트를 grep하고 싶습니다.

나는 이와 같은 것을 생각하고 있었다 :

grep '^#h ' -- "$0" | sed -e 's/#h //'

그러나 스크립트가 PATH에 있고 디렉토리를 명시 적으로 지정하지 않고 호출 된 디렉토리에 있으면 어떻게 될지 궁금했습니다.

특수 변수에 대한 설명을 검색하고 다음에 대한 설명을 찾았습니다 $0.

  • 현재 쉘 또는 프로그램의 이름

  • 현재 스크립트의 파일 이름

  • 스크립트 자체의 이름

  • 실행될 때 명령

이들 중 어느 것도 $0스크립트가 디렉토리없이 호출 된 경우 그 값에 디렉토리가 포함 되는지 여부를 명확하게하지 않습니다 . 마지막 것은 실제로 그렇지 않다는 것을 암시합니다.

내 시스템에서 테스트 (Bash 4.1)

한 줄로 scriptname 이라는 / usr / local / bin에 실행 파일을 만들고 echo $0다른 위치에서 호출했습니다.

이것들은 내 결과입니다 :

> cd /usr/local/bin/test
> ../scriptname
../scriptname

> cd /usr/local/bin
> ./scriptname
./scriptname

> cd /usr/local
> bin/scriptname
bin/scriptname

> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname

> scriptname
/usr/local/bin/scriptname

이 테스트에서 값은 경로 구성 요소없이 호출 된 경우를 제외하고$0 항상 스크립트가 호출 된 방식과 동일 합니다. 이 경우의 값 $0은 IS 절대 경로 . 따라서 다른 명령으로 전달하는 것이 안전 할 것 같습니다.

그러나 스택 오버플로 에 대한 의견이 나에게 혼란 스러웠습니다. 대답은 $(dirname $0)현재 스크립트의 디렉토리를 얻는 데 사용 하는 것이 좋습니다 . 주석 (7 번 찬성)은 "스크립트가 경로에 있으면 작동하지 않습니다"라고 말합니다.

질문

  • 그 의견이 맞습니까?
  • 다른 시스템에서 동작이 달라 집니까?
  • $0디렉토리를 포함하지 않는 상황이 있습니까?

사람들은 $0대본 이외의 다른 상황에 대해 대답했으며 질문 제목에 대답합니다. 그러나 $0스크립트 자체가 있지만 디렉토리를 포함하지 않는 상황에도 관심이 있습니다 . 특히, 나는 SO 답변에 대한 의견을 이해하려고 노력하고 있습니다.
toxalot

답변:


17

가장 일반적인 경우 $0스크립트에 대한 절대 또는 상대 경로를 포함하므로

script_path=$(readlink -e -- "$0")

( readlink명령이 있고 지원 한다고 가정 -e)은 일반적으로 스크립트에 대한 표준 절대 경로를 얻는 데 충분한 방법입니다.

$0 는 인터프리터에 전달 된 스크립트를 지정하는 인수에서 지정됩니다.

예를 들면 다음과 같습니다.

the-shell -shell-options the/script its args

$0을 얻습니다 the/script.

실행할 때 :

the/script its args

당신의 쉘은 :

exec("the/script", ["the/script", "its", "args"])

#! /bin/sh -예를 들어 스크립트에 she-bang이 포함 된 경우 시스템은이를 다음과 같이 변환합니다.

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "the/script", "its", "args"])

(she-bang을 포함하지 않거나 시스템이 ENOEXEC 오류를 반환하면 더 일반적으로 쉘이 동일한 작업을 수행합니다)

일부 시스템에서는 setuid / setgid 스크립트에 예외가 있습니다. 여기서 시스템은 일부 스크립트를 열고 fd x대신 실행합니다.

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "/dev/fd/x", "its", "args"])

경쟁 조건을 피하기 위해 (이 경우 $0포함 /dev/fd/x).

지금, 당신은 그 주장 할 수 /dev/fd/x 있다 해당 스크립트의 경로. 그러나에서 읽는 경우 $0입력을 사용할 때 스크립트가 중단됩니다.

호출 된 스크립트 명령 이름에 슬래시가 포함되어 있지 않으면 차이가 있습니다. 에:

the-script its args

당신의 쉘은에서 찾을 것 the-script입니다 $PATH. $PATH일부 디렉토리에 대한 절대 또는 상대 경로 (빈 문자열 포함)를 포함 할 수 있습니다. 예를 들어, 현재 디렉토리에 $PATH포함되어 /bin:/usr/bin:있고 the-script발견되면 쉘은 다음을 수행합니다.

exec("the-script", ["the-script", "its", "args"])

이것은 될 것입니다 :

exec("/bin/sh", ["/bin/sh" or "the-script", "-", "the-script", "its", "args"]

또는 다음에있는 경우 /usr/bin:

exec("/usr/bin/the-script", ["the-script", "its", "args"])
exec("/bin/sh", ["/bin/sh" or "the-script" or "/usr/bin/the-script",
     "-", "/usr/bin/the-script", "its", "args")

setuid corner case를 제외한 위의 모든 경우 $0스크립트에 대한 경로 (절대 또는 상대)가 포함됩니다.

이제 스크립트를 다음과 같이 호출 할 수도 있습니다.

the-interpreter the-script its args

the-script위와 같이 슬래시 문자를 포함하지 않는, 동작은 쉘에서 쉘에 약간 다릅니다.

오래된 AT & T ksh구현은 실제로 스크립트를 무조건적으로 찾고 $PATH있었으며 (실제로는 setuid 스크립트의 버그 및 보안 구멍이었습니다) $0실제로 조회가 현재 디렉토리에서 발견 되지 않는 한 스크립트 경로를 포함 하지 않았습니다 .$PATHthe-script

최신 AT & T kshthe-script읽을 수있는 경우 현재 디렉토리에서 해석하려고 시도 합니다. 그렇지 않다면 그것은 읽을 수에 대한 조회 할 및 실행 the-script 에서 $PATH.

들어 bash경우 수표, the-script현재 디렉토리에 (그리고 깨진 심볼릭 링크되지 않았 음)으로하지 않을 경우, 조회 (반드시 실행) 읽을위한 the-script에서 $PATH.

zshsh에뮬레이션과 같이 할 것입니다 bash경우 점을 제외하고 the-script현재 디렉토리에 깨진 심볼릭 링크, 그것은 검색하지 않을 the-script$PATH대신 오류를보고합니다.

다른 모든 본쉘이 보이지 않는 the-script에서 $PATH.

어쨌든 모든 셸에서 $0a를 포함하지 /않고 읽을 수 없는 것을 발견하면 에서 찾은 것 같습니다 $PATH. 그런 다음 파일의 $PATH실행 파일 이 될 가능성이 높기 command -v -- "$0"때문에 경로를 찾는 데 사용하는 안전한 근사치 일 수 있습니다 (그러나 $0쉘 내장 또는 키워드 이름 (대부분의 쉘에서) 일 경우 작동하지 않습니다 ).

따라서 실제로 해당 사례를 다루고 싶다면 다음과 같이 작성할 수 있습니다.

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}""; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

( ""첨부 자는 separator 대신 분리 문자$PATH$IFS작동 하는 쉘이있는 후행 빈 요소를 보존하는 것입니다 ).

이제 스크립트를 호출하는 더 난해한 방법이 있습니다. 하나는 할 수 있습니다 :

the-shell < the-script

또는:

cat the-script | the-shell

이 경우 인터프리터가 수신 $0한 첫 번째 인수 ( argv[0])가 되지만 (일반적으로 기본 이름이나 해당 인터프리터에 대한 경로 일 수 있습니다).the-shell

의 가치를 기반으로 상황에 처한 것을 감지하는 $0것은 신뢰할 수 없습니다. ps -o args= -p "$$"단서를 얻기 위해 출력을 볼 수 있습니다. 파이프의 경우 스크립트 경로로 돌아갈 수있는 실제 방법은 없습니다.

하나는 또한 할 수 있습니다 :

the-shell -c '. the-script' blah blih

그런 다음, 제외 zsh(과 Bourne 쉘의 오래된 구현) $0이 될 것입니다 blah. 다시 한 번, 해당 쉘에서 스크립트 경로에 도달하기가 어렵습니다.

또는:

the-shell -c "$(cat the-script)" blah blih

기타

올바른 권한을 갖기 위해 $progname다음과 같이 특정 문자열을 검색 할 수 있습니다.

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}:; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

[ -f "$progname" ] && grep -q 7YQLVVD3UIUDTA32LSE8U9UOHH < "$progname" ||
  progname=unknown

그러나 다시 한 번 노력할 가치가 있다고 생각하지 않습니다.


스테판, "-"위의 예에서 귀하의 사용을 이해하지 못합니다 . 내 경험에 의하면, exec("the-script", ["the-script", "its", "args"])이된다 exec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])통역 옵션 물론 가능성과 함께.
jrw32982는 Monica

@ jrw32982, #! /bin/sh -이다 "항상 사용 cmd -- something당신이 보장 할 수없는 경우 something이 시작되지 않습니다 -" 여기에 적용 좋은 연습 격언 /bin/sh(여기서 -의 끝 옵션 마커보다 더 휴대용이기 때문에 --)으로 something의 경로 / 이름 인 스크립트. 당신은이 setuid 스크립트가 사용하지 않는 경우 (하지만 그들을 지원하는 시스템은 / dev / FD이 질문에 대해 언급 / X 방식), 다음 하나라는 스크립트에 심볼릭 링크를 생성하여 루트 쉘을 얻을 수 있습니다 -i또는 -s대한 예.
Stéphane Chazelas

고마워, 스테판 나는 당신의 예제 shebang 라인에서 후행 단일 하이픈을 놓쳤다. 단일 하이픈이 이중 하이픈 pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html 과 동등한 것으로 문서화 된 곳을 찾아야했습니다 .
jrw32982는 Monica

setbang 스크립트를 위해 shebang 행에서 후행 단일 / 이중 하이픈을 잊어 버리는 것은 너무 쉽습니다. 어떻게 든 시스템은 당신을 위해 그것을 처리해야합니다. setuid 스크립트를 허용 /bin/sh하지 않거나 setuid가 실행되고 있음을 감지 할 수있는 경우 자체 옵션 처리를 비활성화해야합니다. / dev / fd / x 자체를 사용하여 문제를 해결하는 방법을 모르겠습니다. 여전히 단일 / 이중 하이픈이 필요하다고 생각합니다.
jrw32982는 Monica

@ jrw32982는 /dev/fd/x시작 /하지 -. 기본 목표는 두 가지 사이의 경쟁 조건을 제거하는 것입니다 execve()( execve("the-script")특권을 높이고 그 이후 execve("interpreter", "thescript")interpreter스크립트를 나중에 여는 위치 사이에 있음). suid 스크립트 execve("interpreter", "/dev/fd/n")는 첫 번째 execve ()의 일부로 n이 열린 위치를 대신 올바르게 수행 합니다
Stéphane Chazelas

6

디렉토리가 포함 되지 않는 두 가지 상황 이 있습니다.

> bash scriptname
scriptname

> bash <scriptname
bash

두 경우 모두 현재 디렉토리는 scriptname 이 있는 디렉토리 여야합니다 .

첫 번째 경우, FILE 인수가 현재 디렉토리와 관련이 있다고 가정 $0하기 grep때문에 의 값은 여전히 전달 될 수 있습니다 .

두 번째 경우, 도움말 및 버전 정보가 특정 명령 행 옵션에 대한 응답으로 만 인쇄되는 경우 문제가되지 않습니다. 왜 누군가가 도움말이나 버전 정보를 인쇄하는 방식으로 스크립트를 호출하는지 잘 모르겠습니다.

경고

  • 스크립트가 현재 디렉토리를 변경하면 상대 경로를 사용하지 않을 것입니다.

  • 스크립트가 소스 인 경우의 값 $0은 일반적으로 소스 스크립트가 아닌 호출자 스크립트입니다.


3

-c대부분의 (모두?) 쉘에 옵션을 사용할 때 임의의 인수 0을 지정할 수 있습니다 . 예 :

sh -c 'echo $0' argv0

에서 man bash(이것은 내 것보다 더 나은 설명을 가지고 있기 때문에 순전히 선택되었습니다 man sh-사용법은 관계없이 동일합니다) :

-씨

-c 옵션이 있으면 옵션이 아닌 첫 번째 인수 command_string에서 명령을 읽습니다. command_string 뒤에 인수가 있으면 $ 0부터 시작하여 위치 매개 변수에 지정됩니다.


-c 'command'가 피연산자이고 argv0첫 번째 비 연산자 및 명령 줄 인수 이기 때문에 이것이 작동한다고 생각합니다 .
mikeserv

@ mike 맞습니다. 남자 발췌 문장으로 업데이트되었습니다.
Graeme

3

참고 : 다른 사람들은 이미 그 메커니즘을 설명 $0했으므로 모든 것을 건너 뛸 것입니다.

나는 일반적으로이 전체 문제를 회피하고 단순히 명령을 사용한다 readlink -f $0. 이것은 항상 당신이 주장하는 것에 대한 모든 길을 논쟁으로 되돌려 줄 것입니다.

내가 여기부터 시작한다고 가정 해보십시오.

$ pwd
/home/saml/tst/119929/adir

디렉토리 + 파일을 만드십시오 :

$ mkdir adir
$ touch afile
$ cd adir/

이제 과시하기 시작하십시오 readlink:

$ readlink -f ../adir
/home/saml/tst/119929/adir

$ readlink -f ../
/home/saml/tst/119929

$ readlink -f ../afile 
/home/saml/tst/119929/afile

$ readlink -f .
/home/saml/tst/119929/adir

추가 속임수

우리가 심문 할 때 이제 일관된 결과를 반환 $0통해 readlink우리는 간단하게 사용할 수있는 dirname $(readlink -f $0)스크립트 - 또는 절대 경로를 얻기 위해 basename $(readlink -f $0)스크립트의 실제 이름을 얻을 수 있습니다.


0

man페이지는 말합니다 :

$0다음에 확장 nameshellshell script.

이것은 argv[0]현재 쉘 또는 현재 해석중인 쉘이 호출 될 때 공급되는 첫 번째 비 오퍼랜드 명령 행 인수로 해석되는 것으로 보입니다 . 나는 이전에 명시된 sh ./somescript겠습니까 경로의 변수 로 하지만,이 때문에이 올바르지 자신의 새로운 프로세스가 새로운 호출 .$0 $ENV shshell$ENV

이러한 방식으로 현재 환경 에서 실행되는 sh ./somescript.sh것과 다르며 이미 설정되어 있습니다.. ./somescript.sh$0

와 비교 $0하여 확인할 수 있습니다 /proc/$$/status.

echo 'script="/proc/$$/status"
    echo $0
    cat "$script"' \
    > ./script.sh
sh ./script.sh ; . ./script.sh

@toxalot 수정 해 주셔서 감사합니다. 나는 무언가를 배웠다.


내 시스템에서 소스 ( . ./myscript.sh또는 source ./myscript.sh)이면 $0쉘입니다. 그러나 셸 ( sh ./myscript.sh)에 인수로 전달 $0되면 스크립트의 경로입니다. 물론 sh내 시스템에는 Bash가 있습니다. 그래서 그것이 차이가 있는지 모르겠습니다.
toxalot

해시 뱅이 있습니까? 나는 그 차이가 중요하다고 생각하고 그것을 추가 할 수 있다고 생각하지 않고 그것을 exec대신 해야 하지만 대신 소스로 사용해야 exec합니다.
mikeserv

hashbang의 유무에 관계없이 동일한 결과를 얻습니다. 실행 가능 여부에 관계없이 동일한 결과를 얻습니다.
toxalot

나도! 쉘 내장이기 때문이라고 생각합니다. 내가 확인하고 있습니다 ...
mikeserv

뱅 라인은 커널에 의해 해석됩니다. 당신이 간부 인 경우 ./somescriptbangline와 함께 #!/bin/sh, 그것은 실행하는 것과 것이다 /bin/sh ./somescript. 그렇지 않으면 쉘과 아무런 차이가 없습니다.
Graeme

0

상단의 주석 섹션에서 도움말 및 버전 정보를 인쇄 할 수 있도록 현재 스크립트를 grep하고 싶습니다.

$0스크립트 이름이 포함되어 있지만 스크립트가 호출되는 방식에 따라 접두사가 붙은 경로가 포함되어있을 수 있습니다. 필자는 항상 ${0##*/}에서 앞의 경로를 제거하는 도움말 출력에 스크립트 이름을 인쇄하는 데 사용 했습니다 $0.

에서 촬영 고급 Bash 스크립팅 가이드 - 10.2 매개 변수 대체

${var#Pattern}

$var의 가장 짧은 부분에서 $Pattern의 프런트 엔드와 일치하는 부분을 제거하십시오 $var.

${var##Pattern}

$var의 가장 긴 부분에서 $Pattern의 프론트 엔드와 일치하는 부분을 제거하십시오 $var.

따라서 $0일치 하는 가장 긴 부분은 */전체 경로 접두사이며 스크립트 이름 만 반환합니다.


예, 도움말 메시지 에서 사용되는 스크립트 이름에 대해서도 그렇게 합니다. 그러나 나는 스크립트 를 잡는 것에 대해 이야기하고 있으므로 적어도 상대 경로가 필요합니다. 도움말 메시지를 주석 맨 위에 배치하고 나중에 인쇄 할 때 도움말 메시지를 반복하지 않아도됩니다.
toxalot

0

비슷한 것을 위해 내가 사용하는 것 :

rPath="$(dirname $(realpath $0))"
echo $rPath 

rPath=$(dirname $(readlink -e -- "$0"))
echo $rPath 

rPath 항상 같은 값을가집니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.