최고의 이식성을 위해 #! / bin / sh vs #! / bin / bash


36

나는 보통 Ubuntu LTS 서버와 함께 작동 /bin/sh합니다 /bin/dash. symlink /bin/sh에 대한 다른 많은 배포판 /bin/bash.

스크립트가 #!/bin/sh맨 위에 사용되면 모든 서버에서 동일한 방식으로 실행되지 않을 수 있다는 것을 이해 합니다.

서버간에 스크립트의 이식성을 극대화하려는 경우 스크립트에 사용할 셸에 대한 권장 사례가 있습니까?


여러 가지 껍질 사이에는 약간의 차이가 있습니다. 이식성이 가장 중요한 경우 #!/bin/sh, 원래 쉘이 제공 한 것 이외의 것을 사용 하고 사용 하지 마십시오.
Thorbjørn Ravn Andersen에서

답변:


65

쉘 스크립트에 대해 대략 4 가지 수준의 이식성이 있습니다 (shebang 행에 관한 한).

  1. 가장 이식성 : #!/bin/shshebang을 사용 하고 POSIX 표준에 지정된 기본 쉘 구문 사용 하십시오 . 이것은 거의 모든 POSIX / unix / linux 시스템에서 작동합니다. (실제 레거시 Bourne 쉘을 가진 Solaris 10 및 이전 버전을 제외하고 POSIX를 준수하지 않기로했습니다 .)/bin/sh

  2. 두 번째로 가장 휴대하기 쉬운 : #!/bin/bash(또는 #!/usr/bin/env bash) shebang 라인을 사용하고 bash v3 기능을 사용하십시오. 이것은 예상 위치에 bash가있는 모든 시스템에서 작동합니다.

  3. 세 번째로 가장 휴대하기 쉬운 : #!/bin/bash(또는 #!/usr/bin/env bash) shebang 회선을 사용하고 bash v4 기능을 사용하십시오. bash v3가있는 시스템 (예 : 라이센스 이유로 인해 macOS를 사용해야하는 macOS)에서는 실패합니다.

  4. 최소 휴대용 : #!/bin/shshebang을 사용하고 POSIX 쉘 구문에 대한 bash 확장을 사용하십시오. / bin / sh에 대한 bash 이외의 다른 시스템 (예 : 최신 Ubuntu 버전)에서는 실패합니다. 절대 이러지 마십시오. 그것은 단지 호환성 문제가 아니라 명백한 잘못입니다. 불행히도 많은 사람들이 만드는 오류입니다.

내 권장 사항 : 스크립트에 필요한 모든 셸 기능을 제공하는 처음 세 가지 중 가장 보수적 인 것을 사용하십시오. 최대한의 이식성을 위해 옵션 # 1을 사용하지만 내 경험상 배열과 같은 일부 bash 기능은 # 2를 사용하기에 충분히 유용합니다.

당신이 할 수있는 최악의 일은 잘못된 shebang을 사용하는 # 4입니다. 기본 POSIX의 기능과 bash 확장 기능이 확실하지 않은 경우 bash shebang (옵션 # 2)을 사용하거나 매우 기본적인 쉘 (Ubuntu LTS 서버의 대시와 같은)로 스크립트를 철저히 테스트하십시오. 우분투 위키에는 조심해야 할 bashism 목록이 있습니다.

유닉스와 리눅스에서 쉘의 역사와 차이점에 대한 아주 좋은 정보 가 있습니다. "sh 호환이 가능하다는 것은 무엇입니까?" 및 Stackoverflow 질문 "sh와 bash의 차이점" .

또한, 쉘은 시스템마다 다른 것이 아니라는 점에 유의하십시오. 리눅스에 익숙하다면 GNU 명령에 익숙합니다. GNU 명령에는 다른 유닉스 시스템 (예 : bsd, macOS)에서는 찾을 수없는 많은 비표준 확장자가 있습니다. 불행히도 여기에는 간단한 규칙이 없으며 사용하는 명령의 변형 범위를 알아야합니다.

이식성 측면에서 가장 까다로운 명령 중 하나는 가장 기본적인 명령 중 하나입니다 echo. 옵션 (예 : echo -n또는 echo -e) 또는 문자열에서 이스케이프 (백 슬래시)를 사용하여 인쇄 할 때마다 버전마다 다른 작업을 수행합니다. 줄 바꿈없이 문자열을 인쇄하거나 문자열에서 이스케이프 처리하려면 언제든지 printf대신 사용하십시오 (그리고 작동 방식을 배우십시오-실제보다 복잡합니다 echo). ps명령은 또한 엉망 .

주의해야 할 또 다른 일반적인 사항은 명령 옵션 구문에 대한 최근 / GNUish 확장입니다. 이전 (표준) 명령 형식은 명령 뒤에 옵션 (단일 대시가 있고 각 옵션이 단일 문자 임)이 뒤따른다는 것입니다. 명령 인수. 최근의 (그리고 종종 이식성이없는) 변형에는 긴 옵션 (보통으로 도입 --)이 포함되어 옵션이 인수 뒤에 올 수 있고 --옵션을 인수 와 구분 하는 데 사용 됩니다.


12
네 번째 옵션은 단순히 잘못된 생각입니다. 사용하지 마십시오.
pabouk

3
@pabouk 나는 완전히 동의하므로 이것을 명확하게하기 위해 대답을 편집했습니다.
Gordon Davisson

1
첫 번째 진술은 약간 오도합니다. POSIX 표준은 shebang을 사용하여 외부에서 명시하지 않은 동작을 유발한다고 알려주지 않습니다. 또한 POSIX는 posix 쉘의 위치를 ​​지정하지 않고 이름 ( sh) 만 지정 하므로 /bin/sh올바른 경로가 보장되지는 않습니다. 가장 휴대하기 쉬운 것은 Shebang을 전혀 지정하지 않거나 사용 된 OS에 shebang을 적용하는 것입니다.
jlliagre

2
나는 최근에 내 스크립트로 # 4에 빠졌고 왜 작동하지 않는지 알 수 없었습니다. 결국, 동일한 명령이 제대로 작동했으며 쉘에서 직접 시도했을 때 내가 원하는 것을 정확하게 수행했습니다. 최대한 빨리 변경으로 #!/bin/sh#!/bin/bash불구하고, 스크립트는 완벽했다. (내 방어를 위해, 그 스크립트는 시간이 지남에 따라 실제로 sh-ism 만 필요한 스크립트에서 bash와 유사한 동작에 의존하는 스크립트로 발전했다.)
CVn

2
@ MichaelKjörling (true) Bourne 쉘은 Linux 배포판과 거의 번들로 제공되지 않으며 POSIX와 호환되지 않습니다. POSIX 표준 쉘은 kshBourne이 아닌 동작에서 작성되었습니다 . FHS 다음에 나오는 대부분의 Linux 배포판 /bin/sh은 POSIX 호환성을 제공하기 위해 선택한 쉘에 대한 심볼릭 링크 bash이거나 일반적으로 또는 dash입니다.
jlliagre

5

./configureTXR 언어를 빌드하기 위해 준비 하는 스크립트에서 더 나은 이식성을 위해 다음 프롤로그를 작성했습니다. #!/bin/shPOSIX를 준수하지 않는 오래된 Bourne Shell 인 경우에도 스크립트가 부트 스트랩됩니다 . (Solaris 10 VM에서 모든 릴리스를 빌드합니다).

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell

여기서 아이디어는 우리가 실행중인 것보다 더 나은 쉘을 찾고 해당 쉘을 사용하여 스크립트를 다시 실행한다는 것입니다. txr_shell환경 변수는 다시 실행 스크립트가 다시 실행 재귀 인스턴스 알고 그래서, 설정됩니다.

(내 스크립트에서 txr_shell변수는 다음 두 가지 목적으로 계속 사용됩니다. 첫 번째는 스크립트 출력에 유익한 메시지의 일부로 인쇄됩니다. 두 번째로 SHELL변수 는에 변수로 설치 Makefile되므로이를 make사용합니다. 레시피를 실행하기위한 쉘도 있습니다.)

/bin/sh대시 인 시스템에서는 위의 논리가 /bin/bash스크립트를 찾아 다시 실행한다는 것을 알 수 있습니다.

Solaris 10 상자에서 /usr/xpg4/bin/shBash가 없으면 시작됩니다.

프롤로그는 test파일 존재 테스트를 사용하여 보수적 인 쉘 방언으로 작성되었으며, ${@+"$@"}오래된 오래된 쉘 ( "$@"POSIX 호환 쉘에있는 경우)에 대한 인수를 확장 하는 트릭을 작성했습니다 .


x적절한 인용을 사용하고 있다면 그 관용구가 현재 사용되지 않는 테스트 호출 을 여러 테스트 와 결합 -a하거나 -o여러 테스트를 둘러싸고 있기 때문에 해커 가 필요하지 않을 것 입니다.
찰스 더피

@CharlesDuffy 실제로; test x$whatever나는 니스에 양파 같은이 모습을 저지르고있어있다. 우리가 깨진 오래된 셸을 인용하여 신뢰할 수 없다면, 마지막 ${@+"$@"}시도는 의미가 없습니다.
Kaz

2

Bourne 쉘 언어의 모든 변형은 Perl, Python, Ruby, node.js 및 심지어 (논쟁 적으로) Tcl과 같은 최신 스크립팅 언어와 비교할 때 객관적으로 끔찍합니다. 약간 복잡한 작업을 수행해야하는 경우 쉘 스크립트 대신 위의 방법 중 하나를 사용하면 장기적으로 더 행복 할 것입니다.

쉘 언어가 새로운 언어에 비해 여전히 유일하고 유리한 점은 유닉스가 될 수있는 모든 것에 자신을 호출 하는 것이/bin/sh 존재할 수 있다는 것입니다. 그러나 POSIX 호환 제품이 아닐 수도 있습니다. 많은 레거시 독점 유닉스는 Unix95에 의해 요구 된 변경 (예, 20 년 전 Unix95와 계산) 이전/bin/sh기본 PATH에서 구현 된 언어 와 유틸리티를 동결했습니다 . 이 UNIX95의 집합이 될 수도 있고, 당신이 운이 좋다면 심지어 POSIX.1-2001는, 디렉토리 도구가 되지 기본 경로 (예 ) 그러나 그들은 존재 보장되지 않습니다./usr/xpg4/bin

그러나 Perl의 기본 사항은 Bash보다 임의로 선택한 Unix 설치에 있을 가능성이 큽니다 . (내 말은 "펄의 기본"으로는 /usr/bin/perl존재하고 있습니다 약간의 펄 5, 아마도 꽤 오래된 버전, 그리고 네 운이 경우 인터프리터의 버전과 함께 제공하는 것이 모듈의 설정도 가능합니다.)

따라서:

"구성"스크립트와 같이 유닉스 가되기 위해 어디에서나 작동 해야하는 것을 작성 하는 경우을 사용해야 #! /bin/sh하며 확장을 사용하지 않아도됩니다. 요즘 나는이 상황에서 POSIX.1-2001 호환 쉘을 작성할 것이지만, 누군가가 녹슨 철에 대한 지원을 요청하면 POSIXism을 패치 할 준비가되어있을 것입니다.

당신이있는 경우 그러나 없는 모든 곳에서 작동하는 무언가를 쓰고, 다음 순간 당신은 전혀 Bashism를 사용하도록 유혹, 당신은 중지해야하고 대신 더 나은 스크립팅 언어에 전체 일을 다시 작성합니다. 미래의 자기 자신이 감사합니다.

(따라서 Bash 확장을 사용하는 것이 적절한 시기 ? 첫 번째 순서 : 절대. 두 번째 순서 : Bash 대화식 환경을 확장하기 위해서만 – 예를 들어 스마트 탭 완성 및 고급 프롬프트 제공.)


다른 날에는 다음과 같은 스크립트를 작성해야했습니다 find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. bashisms ( read -d "") 및 GNU 확장 ( find -print0, tar --null) 없이는 제대로 작동하지 않을 수 있습니다 . Perl에서 해당 라인을 다시 구현하면 훨씬 길고 더 어려워집니다. 이것은 bashisms 및 기타 비 POSIX 확장을 사용하는 것이 올바른 일 일 때의 상황입니다.
michau

@michau 그 줄임표에 따라 더 이상 하나의 라이너로 적합하지 않을 수도 있지만 문자 그대로 "더 나은 스크립팅 언어로 전체 내용을 다시 작성하십시오"라고 생각하지 않습니다 . 내 방식은 perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdbashism이 필요하지 않을뿐만 아니라 GNU tar 확장이 필요하지 않은 Notice 와 같습니다 . (명령 줄 길이가 문제이거나 스캔과 타르를 병렬로 실행하려면 필요합니다 tar --null -T.)
zwol

문제는,되는 find스크립트 가능성이 인수 길이 제한을 칠 그래서, 내 시나리오에서 50,000 파일 이름을 반환했습니다. 비 POSIX 확장을 사용하지 않는 올바른 구현은 tar 출력을 점진적으로 빌드하거나 Perl 코드에서 Archive :: Tar :: Stream을 사용해야합니다. 물론 가능하지만,이 경우 Bash 스크립트는 훨씬 빠르게 작성하고 상용구가 적으므로 버그가 적습니다.
michau

BTW, Bash 대신 Perl을 빠르고 간결하게 사용할 수 있습니다 find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. 그러나 질문은 다음과 같습니다 : 1) 어쨌든 사용 find -print0하고 tar --null있으므로 bashism을 피하는 요점 read -d ""은 정확히 같은 종류입니다 (펄 분리기와 달리 언젠가 POSIX 일 수있는 null 구분자를 처리하기위한 GNU 확장) )? 2) Perl은 Bash보다 더 널리 퍼져 있습니까? 최근에 Docker 이미지를 사용하고 있으며 많은 이미지에는 Bash가 있지만 Perl은 없습니다. 고대 Unices보다 새로운 스크립트가 실행될 가능성이 높습니다.
michau
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.