답변:
쉘 스크립트에 대해 대략 4 가지 수준의 이식성이 있습니다 (shebang 행에 관한 한).
가장 이식성 : #!/bin/sh
shebang을 사용 하고 POSIX 표준에 지정된 기본 쉘 구문 만 사용 하십시오 . 이것은 거의 모든 POSIX / unix / linux 시스템에서 작동합니다. (실제 레거시 Bourne 쉘을 가진 Solaris 10 및 이전 버전을 제외하고 POSIX를 준수하지 않기로했습니다 .)/bin/sh
두 번째로 가장 휴대하기 쉬운 : #!/bin/bash
(또는 #!/usr/bin/env bash
) shebang 라인을 사용하고 bash v3 기능을 사용하십시오. 이것은 예상 위치에 bash가있는 모든 시스템에서 작동합니다.
세 번째로 가장 휴대하기 쉬운 : #!/bin/bash
(또는 #!/usr/bin/env bash
) shebang 회선을 사용하고 bash v4 기능을 사용하십시오. bash v3가있는 시스템 (예 : 라이센스 이유로 인해 macOS를 사용해야하는 macOS)에서는 실패합니다.
최소 휴대용 : #!/bin/sh
shebang을 사용하고 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 확장입니다. 이전 (표준) 명령 형식은 명령 뒤에 옵션 (단일 대시가 있고 각 옵션이 단일 문자 임)이 뒤따른다는 것입니다. 명령 인수. 최근의 (그리고 종종 이식성이없는) 변형에는 긴 옵션 (보통으로 도입 --
)이 포함되어 옵션이 인수 뒤에 올 수 있고 --
옵션을 인수 와 구분 하는 데 사용 됩니다.
sh
) 만 지정 하므로 /bin/sh
올바른 경로가 보장되지는 않습니다. 가장 휴대하기 쉬운 것은 Shebang을 전혀 지정하지 않거나 사용 된 OS에 shebang을 적용하는 것입니다.
#!/bin/sh
에 #!/bin/bash
불구하고, 스크립트는 완벽했다. (내 방어를 위해, 그 스크립트는 시간이 지남에 따라 실제로 sh-ism 만 필요한 스크립트에서 bash와 유사한 동작에 의존하는 스크립트로 발전했다.)
ksh
Bourne이 아닌 동작에서 작성되었습니다 . FHS 다음에 나오는 대부분의 Linux 배포판 /bin/sh
은 POSIX 호환성을 제공하기 위해 선택한 쉘에 대한 심볼릭 링크 bash
이거나 일반적으로 또는 dash
입니다.
./configure
TXR 언어를 빌드하기 위해 준비 하는 스크립트에서 더 나은 이식성을 위해 다음 프롤로그를 작성했습니다. #!/bin/sh
POSIX를 준수하지 않는 오래된 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/sh
Bash가 없으면 시작됩니다.
프롤로그는 test
파일 존재 테스트를 사용하여 보수적 인 쉘 방언으로 작성되었으며, ${@+"$@"}
오래된 오래된 쉘 ( "$@"
POSIX 호환 쉘에있는 경우)에 대한 인수를 확장 하는 트릭을 작성했습니다 .
x
적절한 인용을 사용하고 있다면 그 관용구가 현재 사용되지 않는 테스트 호출 을 여러 테스트 와 결합 -a
하거나 -o
여러 테스트를 둘러싸고 있기 때문에 해커 가 필요하지 않을 것 입니다.
test x$whatever
나는 니스에 양파 같은이 모습을 저지르고있어있다. 우리가 깨진 오래된 셸을 인용하여 신뢰할 수 없다면, 마지막 ${@+"$@"}
시도는 의미가 없습니다.
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 확장을 사용하는 것이 올바른 일 일 때의 상황입니다.
perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd
bashism이 필요하지 않을뿐만 아니라 GNU tar 확장이 필요하지 않은 Notice 와 같습니다 . (명령 줄 길이가 문제이거나 스캔과 타르를 병렬로 실행하려면 필요합니다 tar --null -T
.)
find
스크립트 가능성이 인수 길이 제한을 칠 그래서, 내 시나리오에서 50,000 파일 이름을 반환했습니다. 비 POSIX 확장을 사용하지 않는 올바른 구현은 tar 출력을 점진적으로 빌드하거나 Perl 코드에서 Archive :: Tar :: Stream을 사용해야합니다. 물론 가능하지만,이 경우 Bash 스크립트는 훨씬 빠르게 작성하고 상용구가 적으므로 버그가 적습니다.
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보다 새로운 스크립트가 실행될 가능성이 높습니다.
#!/bin/sh
, 원래 쉘이 제공 한 것 이외의 것을 사용 하고 사용 하지 마십시오.