[Shell Script] 쉘 스크립트 (bash) 개발자가 빠지기 쉬운 함정 (2)

정보

  • 업무명     : 쉘 스크립트 (bash) 개발자가 빠지기 쉬운 함정 (2)

  • 작성자     : 박진만

  • 작성일     : 2020-03-22

  • 설   명      :

  • 수정이력 :

 

 요약

[특징]

  • 쉘 스크립트 개발자가 빠지기 쉬운 함정을 소개.

 

[활용 자료]

  • 없음

 

[자료 처리 방안 및 활용 분석 기법]

  • 없음

 

[사용 언어]

  • Bash Script

 

 내용

  • 이 페이지는 Bash 프로그래머가 흔히 발생하는 오류에 대해 요약하였습니다.

  • 아래의 모든 코드의 예시는 어떤 결함을 가지고 있습니다.

  • 들어가기에 앞서 기본적으로 따옴표 (“)를 항상 사용하고, 또 절대로 단어 분할을 사용하지 않는다면 대부분의 함정에 빠지지 않을 수 있습니다.

  • 따옴표를 사용하지 않았을 때의 단어 분할이 되는 이유는 기본적으로 켜져 있는, Bourne쉘에서 상속받은 레거시 코드의 설계 오류 때문입니다.

  • 따라서 하단에서 소개할 결함의 대부분은 이 따옴표와 단어 분할에 관련된 문제가 대부분입니다.

 

11. if [ [ a = b ] && [ c = d ] ]; then ...

 

  • 위 예제의 경우 ifC 언어 같은 조건절의 종류의 사이에 위치하는 구문 마커가 없습니다.

  • 그리고 그룹화도 없습니다. C 언어 같은 if명령을 사용하는 것은 불가능 하고 대신 괄호 []이지 대체하는 것으로는 Bash 명령을 수행 할 수 없습니다!

  • 만약 합성 조건식을 표현하고자 한다면 다음과 같이 하시면 됩니다.

if [ a = b ] && [ c = d ]; then ...

 

  • if다음에 &&(논리적 AND 단축 된 평가 식) 연산자로 연결 한 2 개의 명령이 있다는 것을 기억하십시오. 이것은 아래의  것과 완전히 동일합니다.

if test a = b && test c = d; then ...

 

  • 만약 첫 번째 test명령이 false로 응답해도 if문 본문에는 아무것도 입력되지 않습니다.

  • 즉 true를 반환할 때 두 번째 test명령이 실행됩니다. 그리고 true를 돌려주는 경우에는 if문 본문은 입력됩니다. (C 프로그래머는 &&익숙한 것입니다 .Bash 같은 단축 된 평가 식을 사용합니다. 마찬가지로 || OR연산이 단축 된 평가 식입니다.)

  • [[ 키워드 &&의 사용을 허용하기 때문에 다음과 같이 쓸 수 있습니다 :

if [[ a = b && c = d ]]; then ...

 

  • 평가 연산자와 함께 사용되는 test에 대해서는 함정 # 6을 참조하십시오.

 

12. read $foo

  • read명령은 변수 이름 앞에 $를 사용할 수 없습니다. 만약 foo라는 변수에 데이터를 넣고 싶다면 다음과 같이해야합니다

read foo

 

  • 혹은 더 안전하게 다음과 같이합니다.

IFS= read -r foo

 

  • read $foo입력 행을 로드하려고 하고 그것을 $foo 안에 있는 이름에 대입하려고 하는 작업을 수행하고자 할 때, 즉 당신이 foo다른 변수에 참조되도록 의도하는 경우에 유용합니다. 그러나 대부분의 경우 이것은 단순히 버그입니다.

 

13. cat file | sed s/foo/bar/ > file

  • 파일에서 읽기, 쓰기를 같은 파이프 라인에서 수행 할 수 없습니다.

  • 경우에 따라서 그 파이프 라인이 무엇을 할 것인가에 따라, 파일을 덮어버리게 될 수 있습니다.

  •  또는 그것이 가능 디스크 공간을 가득 찰 때까지 크게 될 수 있으며, OS의 파일 크기 제한에 도달하거나 할당량 제한에 도달 등하는 것입니다.

  • 만약 안전하게 파일에 추가하고 싶다면, 파일의 마지막에 추가하는 것 외에, 텍스트 편집기를 사용하십시오.

printf %s\\n ',s/foo/bar/g' w q | ed -s file

 

  • 만약 텍스트 편집기에서 무언가를 할 경우 어느 시점에서 만들어진 임시 파일이 필요합니다. (*) 예를 들어 다음 예제는 모범 사례라 할 수 있습니다.]

 

sed 's/foo/bar/g' file > tmpfile && mv tmpfile file

 

  • 아래의 예는 GNU sed 4.X 계에서만 작동합니다 :

sed -i 's/foo/bar/g' file(s)

 

  • 다음 대체 명령은 perl 5.X 시스템을 필요로합니다 (GNU sed 4.X 계에서는 사용할 수 없습니다.)

perl -pi -e 's/foo/bar/g' file(s)

 

  • (*) : moreutils sponge 명령은 그 설명서에서 다음 예를 사용하고 있습니다 :

sed '...' file | grep '...' | sponge file
  • mv명령의 불가분성 이외에 임시 파일을 이용하는 것보다 이 버전은 모든 데이터를 파일을 열고 쓰기 전에 "담가"입니다 (실제 문서의 표현입니다!).

  • 처리 시점에서 원본 파일의 복사본이 디스크에 없기 때문에 이 버전은 프로그램이 쓰기 연산에서 충돌 한 경우에는 데이터 손실을 일으 킵니다.

  • 임시 파일과 mv를 사용하는 것은 전원 차단이나 시스템 충돌시에도 극히 적은 위험 만 있습니다. 전원 차단시에도 새 파일이나 기존 파일 중 하나가 남도록 100 % 보장하려면 mv전에 sync해야합니다.

 

14. echo $foo

  • 위의 문제가 없어 보이는 이 명령은 엄청난 혼란을 일으킵니다. 왜냐하면 $foo이 인용 되지 않고, 단어 분리를 일으킬 뿐 아니라 파일의 glob 도 생기기 때문입니다.

  • 이것은 Bash 프로그래머들에게 하여금 그들의 변수가 잘못된 값을 포함시키는 것처럼 착각시킵니다.

  • 비록 실제로는 값에 문제가 없었다고 해도. 단어 분할 또는 파일 이름 전개는 무슨 일이 일어나고 있는지 혼란될 수 있습니다.

msg="Please enter a file name of the form *.zip"

echo $msg

 

  • 이 메시지는 두 단어로 분할되고, glob 역시 전개됩니다, 예를 들어 *.zip같은 것도. 당신의 사용자가 이 메시지를 봤을 때 어떻게 생각할까요?

Please enter a file name of the form freenfss.zip lw35nfss.zip

 

  • 예시를 보여드리겠습니다.

var="*.zip" # var contains an asterisk, a period, and the word "zip"

echo "$var" # writes *.zip

echo $var # writes the list of files which end with .zipvar="*.zip" # var contains an asterisk, a period, and the word "zip"echo "$var" # writes *.zipecho $var # writes the list of files which end with .zip

 

  • 사실 여기에서 echo명령은 절대적인 안전성이있는 상태에서 사용될 수 없습니다. 예를 들어 만약 변수가 -n를 포함하는 경우 echo는 그것을 표시되는 데이터가 아니라 옵션으로 해석

  • 합니다. 변수의 값을 표시하는 가장 절대적으로 확실한 방법은 printf을 사용하는 것입니다.

printf "%s\n" "$foo"

 

15. $foo=bar

  • $변수 이름 앞에 붙여 변수를 할당 할 수 없습니다.

 

16. foo = bar

  • 변수에 값을 할당 할 때 공백 주위에 =을 쓸 수 없습니다. C는 가능하지만 당신이 foo = bar쓴 때, shell은 그들을 3 개의 단어로 나눕니다

  • 마찬가지로 다음 예제도 잘못된 것입니다 :

foo= bar # 잘못된 코드

foo =bar # 잘못된 코드

$foo = bar; # 완전히 잘못된 코드

foo=bar # 옳은코드

foo="bar" # 매우 좋은 코드foo= bar # 잘못된 코드foo =bar # 잘못된 코드$foo = bar; # 완전히 잘못된 코드foo=bar # 옳은코드foo="bar" # 매우 좋은 코드

 

17. echo <<EOF

  • 여기에서 문서 스크립트에서 텍스트 데이터의 큰 블록을 포함하는 유용한 도구입니다. 이것은 스크립트의 텍스트 행을 명령의 표준 출력으로 리디렉션 합니다.

  • 불행히도 이것은 echo 표준 입력에서 읽을 수 없습니다.

# 잘못된 예시

echo <<EOF

Hello world

How's it going?

EOF

# 좋은 예시

cat <<EOF

Hello world

How's it going?

EOF

# Or, use quotes which can span multiple lines (efficient, echo is built-in):

echo "Hello world

How's it going?"# 잘못된 예시echo <<EOFHello worldHow's it going?EOF# 좋은 예시cat <<EOFHello worldHow's it going?EOF# Or, use quotes which can span multiple lines (efficient, echo is built-in):echo "Hello worldHow's it going?"

 

  • 따옴표를 이렇게 사용하는 것은 문제가되지 않습니다, 모든 쉘에서 제대로 동작합니다.

  • 그러나 행의 나눔을 스크립트에 떨어뜨리는 것은 불가능합니다.

  • 처음이자 마지막 행에 구문 태그가 있습니다. 만약 쉘 구문에서 만지지 행이있는 경우에는 또는 cat명령을 spawn하고 싶지 않다면 여기에 대안이 있습니다

# 대안으로 printf 를 사용할 수도 있음

printf %s "\

Hello world

How's it going?# 대안으로 printf 를 사용할 수도 있음printf %s "\Hello worldHow's it going?

 

  • 위의 예시에서 첫 번째 줄의 \텍스트 블록의 시작 부분에 불필요한 줄 바꿈을 추가하는 것을 방지합니다.

  • 마지막 줄에 줄 바꿈이 포함됩니다 (마지막 쿼터는 줄 바꿈과 함께 있기 때문입니다.).

  • printf형식 인수의 \n를 사용하지 않는 것은 printf이 마지막으로 불필요한 행을 추가하는 것을 방지합니다.

  • \트릭은 작은 따옴표 내에서 작동하지 않습니다. 만약 텍스트 덩어리 주위에 작은 따옴표를 사용하고 / 릏 사용할 필요가있는 경우에는 2 개의 옵션이 있습니다

printf %s \

'Hello world'

printf %s 'Hello world

 

18. su -c 'some command'

  • 이 구문은 거의 맞습니다. 그러나 문제는 많은 플랫폼에서 su-c 인수를 취하 만, 이 경우 쉘에서 결과가 나도지 않습니다.

$ su -c 'echo hello'

 

 

  • -c 'some command'쉘에 전달하려면 사용자 이름을 -c앞에 붙여야합니다.

su root -c 'some command' # Now it's right.

 

  • 이 명령을 사용한 경우에는 suroot라는 사용자 이름이 있다고 간주합니다 그러나 Shell 명령을 나중에 전달하려는 경우 반드시 사용자 이름을 제공해야합니다.

 

19. cd /foo; bar

  • 만약 cd명령에서 오류를 확인하지 않는 경우, bar잘못된 장소에서 실행되고 종료 될 것입니다. 예를 들어 만약 barrm -f *있다면, 이것은 대단한 재해를 야기 할 수 없습니다.

  • cd명령에서 오류가 있는지 항상 확인하는 것이 필요합니다. 가장 간단한 작업을 수행하는 방법은 다음과 같습니다.

cd /foo && bar

 

  • 만약 cd후에 하나 이상의 명령이 있는 경우에는 아래의 예시가 바람직합니다.

cd /foo || exit 1

bar

baz

bat ... # Lots of commands.cd /foo || exit 1barbazbat ... # Lots of commands.

 

  • cd 디렉토리의 변경의 실패를 "bash : cd : / foo : No such file or directory"와 같은 표준 오류 출력 메시지와 함께 알려줍니다. 하지만 표준 출력에 어떤 메시지를 추가하고 싶다면 다음과 같이 명령 그룹화 사용할 수 있습니다.

cd /net || { echo "Can't read /net. Make sure you've logged in to the Samba network, and try again."; exit 1; }

do_stuff

more_stuffcd /net || { echo "Can't read /net. Make sure you've logged in to the Samba network, and try again."; exit 1; }do_stuffmore_stuff

 

  • {echo의 사이에 공간이 필요한 것, 그리고 }에 닫기 전에 ;이 필요한 것을 기억하십시오

  • 0이 아닌 코드를 반환 명령 중 하나에 스크립트를 도중에 중단하기 위해 "set -e ' 를 사용하는 것을 선호하는 사람들도 있지만, 이를 제대로 사용하는 것은 조금 까다롭습니다

  • 그런데 많은 Bash 스크립트 내부에서 디렉토리를 변경하고 있다면, Bashpushd, popd, dirs에 대한 도움말을 읽는 것이 좋습니다. 아마 당신이 쓴 cdpwd관리하는 모든 코드는 완전히 필요없는 것들 일 것이다.

  • 이것을 말하기는 아래의 코드를 보고 비교해봅시다.

find ... -type d -print0 | while IFS= read -r -d '' subdir; do

here=$PWD

cd "$subdir" && whatever && ...

cd "$here"

done

비교 대상입니다 :

find ... -type d -print0 | while IFS= read -r -d '' subdir; do

(cd "$subdir" || exit; whatever; ...)

donefind ... -type d -print0 | while IFS= read -r -d '' subdir; do here=$PWD cd "$subdir" && whatever && ... cd "$here"done비교 대상입니다 :find ... -type d -print0 | while IFS= read -r -d '' subdir; do (cd "$subdir" || exit; whatever; ...)done

 

  • 루프의 다음 반복은 cd성공 실패에 관계없이 정상 위치로 돌아갑니다.

  • 수동으로 다시 조작할 필요는없고, 또한 끝나지 않는 다른 조건식의 사용을 방지 &&로직의 반복으로 채우기도 없습니다. Subshell 버전은 간단하고 깨끗합니다

 

20. [ bar == "$foo" ]

  • ==연산자는 [명령에 유효하지 않습니다. 대신, =또는 [[를 사용하십시오.

[ bar = "$foo" ] && echo yes

[[ bar == $foo ]] && echo yes

 

 

 

[Shell Script] 쉘 스크립트 (bash) 개발자가 빠지기 쉬운 함정 (3)

정보 업무명 : 쉘 스크립트 (bash) 개발자가 빠지기 쉬운 함정 (2) 작성자 : 박진만 작성일 : 2020-03-22 설 명 : 수정이력 : 요약 [특징] 쉘 스크립트 개발자가 빠지기 쉬운 함정을 소개. [활용 자료] 없음 [자료..

shlee1990.tistory.com

 

 참고 문헌

[논문]

  • 없음

[보고서]

  • 없음

[URL]

  • 없음

 

 문의사항

[기상학/프로그래밍 언어]

  • sangho.lee.1990@gmail.com

[해양학/천문학/빅데이터]

  • saimang0804@gmail.com

 

 

 

 

 

본 블로그는 파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음