정보

    • 업무명     : 쉘 스크립트 (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

     

     

     

     

     

    본 블로그는 파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기