반응형

    정보

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

    • 작성자     : 박진만

    • 작성일     : 2020-03-22

    • 설   명      :

    • 수정이력 :

     

     요약

    [특징]

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

     

    [활용 자료]

    • 없음

     

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

    • 없음

     

    [사용 언어]

    • Bash Script

     

     내용

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

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

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

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

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

     

    21. for i in {1..10}; do ./something &; done

     

    • ; & 뒤에 사용할 수 없습니다. 아래의 코드처럼 ;을 제거하십시오.

    for i in {1..10}; do ./something & done

     

    • 또는 다음과 같이 합니다.

    for i in {1..10}; do
    
    ./something &
    
    done
    
    

     

    • 이는 &;같은 명령을 종료시키는 역할로 기능하고 있기 때문입니다. 따라서 이 두 가지를 혼합 할 수 없습니다.

     

    22. cmd1 && cmd2 || cmd3

    • &&||if ... then ... else ... fi바로 가기 구문으로 사용하는 사람이 있습니다. 일반적으로 많은 경우에 이것은 완벽하게 안전합니다.

    [[ -s $errorlog ]] && echo "Uh oh, there were some errors." || echo "Successful."

     

    • 그러나 일반적으로 이 구성은 if ... fi와 다른 방식이라 할 수 있습니다.

    • && 또한 종료 상태를 생성 한 후 명령이 전달됩니다.

    • 즉 만약 종료 상태 "true"(0)이 아닌 경우에는 || 다음의 명령도 작동하지 않게 됩니다. 예를 들면 다음과 같습니다.

    i=0
    
    true && ((i++)) || ((i--))
    
    echo $i # Prints 0

     

    • 여기에 무슨 일이 있을까요?

    • i1이 되어야 할 것처럼 보이지만 실제로는 0이 출력됩니다.

    • 왜냐하면 그것은 i++그리고 i--이 모두 실행 되었기 때문입니다.

    • ((i++))명령은 종료 상태를 가지고 있고 그 종료 상태는 괄호 안의 C 언어 같은 평가의 표현에서 제공됩니다.

    • 이 표현의 값은 0 ( i초기 값)에서 C에서 int 형의 0false 받아 들여지고 있습니다.

    • , ((i++))( i0 일 때) 종료 상태는 1이고 (false) 그래서 ((i--))명령도 실행되는 것입니다.

    • 이것은 전치 증가 연산자를 이용한 경우 ++i의 종료 상태가 true가 아니기 때문에 발생하지 않습니다

    i=0
    
    true && (( ++i )) || (( --i ))
    
    echo $i # Prints 1

     

    • 그러나 이것은 예의 포인트를 잃고 있습니다.

    • 이것은 살얼음판 위에서 동작하고 있으며, 만약 실패할 가능성이 있다면, x && y || z if y에 의지 할 수 없습니다! (이 예에서는 만약 i0으로 바꾸어 -1에서 초기화했다면 실패하게 됩니다.)

    • 만약 안전성이 필요한 경우 또는 이것이 어떻게 움직이는 지 확실하지 않는다면, 혹은 어딘가 절 무언가가 명확하지 않은 경우, 단순 if ... fi구문을 프로그램 내부에서 사용하십시오.

    i=0
    
    if true; then
    
    ((i++))
    
    else
    
    ((i--))
    
    fi
    
    echo $i # Prints 1

     

    • 이 절은 Bourne Shell 마찬가지 그것을 나타내는 코드가 여기에 있습니다.

    true && { echo true; false; } || { echo false; true; }

     

    23. echo "Hello World!"

    • 여기에서 문제는 대화 형 Bash 쉘에서 다음과 같은 오류가 발생하는 것입니다.

    • 이것은 대화 형 쉘의 기본 설정에서는 Bashcsh-style 명령 히스토리 확장을 !을 이용하여 사용하기 때문에 발생합니다. 이것은 쉘 스크립트의 문제가 아니라 대화 형 쉘 만에서 발생하는 문제입니다.

    • 이 현상에 대한 해결법은 아래와 같습니다.

    $ echo "hi\!"
    
    hi\!

     

    • 가장 간단한 해결 방법은 histexpand옵션을 설정하지 않는 것입니다. 이것은 set +Hset +o histexpand에서 실시 할 수 있습니다.

    • 그런데, 여기서 문제입니다. histexpand는 작은 따옴표보다 우선되는 것일까요? 나는 mp3 파일을 조작 할 때 개인적으로이 문제가 발생했습니다.

    mp3info -t "Don't Let It Show" ...
    
    mp3info -t "Ah! Leah!" ...

     

    • 작은 따옴표를 사용하는 것은 매우 불편합니다.

    • 그러나 큰 따옴표를 사용하면 명령 히스토리 확장의 문제에 해당됩니다. (그리고 파일이 아포스트로피 및 !모두를 포함하는 것을 상상해보십시오. 인용에 의한 구분은 심한 것입니다.)

    • 정말 명령 히스토리 확장을 사용한 적이 없기 때문에 개인적인 취향 치아 ~/.bashrc에서 명령 히스토리 확장을 비활성화하는 것입니다.

    • 이 경우 다음의 명령에 유효합니다 :

    echo 'Hello World!'

     

    • 또는

    set +H
    
    echo "Hello World!"set +Hecho "Hello World!"

     

    • 또는

    histchars=

     

    • 많은 사람들은 간단하게 ~/.bashrc안쪽에 set +H또는 set +o histexpand설정 명령 히스토리 확장을 해제하는 것을 선택합니다.

    • 이것은 개인적인 취향이므로 어떤 방법이 여러분에게 맞는 있는지 선택하면 됩니다.

    • 다른 해결 방법으로는 다음이 있습니다 :

    exmark='!'
    
    echo "Hello, world$exmark"

     

    24. for arg in $ *

    • Bash (모든 Bourne )은 위치 매개 변수의 목록을 하나씩 볼 수있는 특별한 구문을 가지고 있습니다.

    • 그리고 $* 같은 경우 위와 같은 일을 수행하지 않습니다. 또한 $@도 그렇지는 않습니다. 모두 스크립트 만약 파라미터에서 단어 목록에 확장하고 다른 단어로 각각의 매개 변수에 배포하지 않습니다.

    • 올바른 구문은 다음과 같습니다.

    for arg in "$@"
    
    # Or simply:
    
    for argfor arg in "$@"# Or simply:for arg

     

    • 위치 매개 변수에서 루프 할 스크립트에서 다음과 같게하는 것이 일반적이며, for arg기본적으로 for arg in "$@"같은 느낌입니다.

    • 여기서 큰 따옴표 된 "$@"각각의 변수를 하나의 단어로 취급할 수 있습니다.

    • 여기에 예제가 있습니다.

    # 올바르지 않은 방법
    
    for x in $*; do
    
    echo "parameter: '$x'"
    
    done# Incorrect version for x in $*; do echo "parameter: '$x'" done
    $ ./myscript 'arg 1' arg2 arg3
    
    parameter: 'arg'
    
    parameter: '1'
    
    parameter: 'arg2'
    
    parameter: 'arg3'

     

     

    • 이것은 다음과 같이 작성해야합니다.

    #올바른 방법
    
    for x in "$@"; do
    
    echo "parameter: '$x'"
    
    done
    
    
    
    $ ./myscript 'arg 1' arg2 arg3
    
    parameter: 'arg 1'
    
    parameter: 'arg2'
    
    parameter: 'arg3'

     

    25. function foo ()

    • 이것은 일부 Shell에서 작동합니다만, 작동하지 않을수도 있습니다.

    • 왜냐하면 함수를 정의 할 때 키워드 기능을 () 결합하는 것이 아니기 때문입니다.

    • Bash (적어도 일부 버전에서) 두 가지를 혼합하는 것을 허용하고 있습니다.

    • 그러나 대부분의 Shell은 이를 허용하지 않습니다

    • 일부 Shell은 위의 foo함수를 허용하지만, 이동성 극대화를 위해 다음과 같은 것을 권장합니다.

    foo() {
    
    ...
    
    }

     

    26. echo "~"

    • 물결표 확장은 ~이 인용되지 않은 경우에만 유효합니다. 이 예제에서는 echo~을 사용자의 홈 디렉토리 경로가 아니라 ~으로 표준 출력에 기록합니다

    • 인용 된 경로의 매개 변수로 사용자 홈 디렉토리의 상대 경로를 표현하려면 ~대신 $HOME을 사용해야합니다. 예를 들어 $HOME/home/my photos경우를 예로 들어 봅시다.

    "~/dir with spaces" # "~/dir with spaces"
    
    ~"/dir with spaces" # "~/dir with spaces"
    
    ~/"dir with spaces" # "/home/my photos/dir with spaces"
    
    "$HOME/dir with spaces" # "/home/my photos/dir with spaces"

     

    27. local varname = $ (command)

    • 로컬 변수를 함수 안에서 선언 할 때 local명령으로 자신의 권한에 따라 행동합니다.

    • 이것은 이상하게도 다른 행에 영향을 줄 수 있습니다.

    • 예를 들어, 명령 치환의 종료 상태 ( $?)을 취득하려고해도 할 수 없습니다. local종료 상태가 그것을 대체하게됩니다.

    • 다음과 같이 다른 명령을 사용하는 것이 좋습니다.

    local varname
    
    varname=$(command)
    
    rc=$?local varname varname=$(command) rc=$?

     

    • 다음 함정에서 구문에 관한 다른 문제를 설명합니다.

     

    28. export foo = ~ / bar

    • 물결표 열기 (사용자 이름과 함께 또는 사용자 이름없이) 물결표가 문자열의 처음 또는 슬래시 후, 또는 물결표 만 때만 발생하는 것이 보증되고 있습니다.

    • 또한 =의한 변수에 대입 직후 물결이 나타나는 경우도 보증되고 있습니다.

    • 그러나 exportlocal명령은 이러한 대입을 구성하지 않습니다.

    • , 어떤 쉘 (Bash 같은)export foo=~/bar물결표의 전개가 이루어집니다.

    foo=~/bar; export foo # Right!
    
    export foo="$HOME/bar" # Right!

     

    29. sed 's / $ foo / good bye /'

    • 작은 따옴표 안에, $foo같은 bash 매개변수가 할당되지 않습니다.

    • 왜냐하면 작은 따옴표 목적으로 $ 같은 문자를 쉘로부터 보호하기 위해 내장된 기능 때문입니다.

    • 이 경우 작은 따옴표를 큰 따옴표로 변경하면 해결 됩니다.

    foo="hello"; sed "s/$foo/good bye/"

     

    • 그러나 큰 따옴표를 사용하는 경우 더 이스케이프해야 할 수도 있음을 기억하십시오

     

     

    30. tr [AZ] [az]

    • 여기에는 (적어도) 3 가지 잘못된 점이 있습니다. 첫 번째 문제는 [A-Z]그리고 [a-z]쉘은 glob 처럼 보이는 것입니다.

    • 만약 현재 디렉토리에 글자의 파일 이름의 파일이 없으면 명령이 작동되는 것처럼 보이는 것입니다. 하지만 해 보면 실행 결과는 실패합니다.

    • 두 번째 문제는 tr의 반드시 올바른 작성은 없다는 것입니다

    • 세 번째 문제는 로케일 의존에서 A-Z또는 a-z기대 한대로 26 문자 ASCII 문자열을 미치지 않을 수 있다는 것입니다. 실제로 일부 로케일에서는 z알파벳의 중간 문자입니다! 이 해결책은 무엇을하고 싶은가에 따라 달라집니다.

    # 26 문자 라틴 배열을 변경하고자하는 경우에는 이것을 사용해주세요
    
    LC_COLLATE=C tr A-Z a-z
    # 사용자가 기대하는 것에 가까운 로캘 종속 변환을 원하는 경우에는 이것을 사용해주세요
    tr '[:upper:]' '[:lower:]'

     

    [다른편 링크]

    • 1편

     

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

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

    shlee1990.tistory.com

     

    • 2편

     

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

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

    shlee1990.tistory.com

     

     참고 문헌

    [논문]

    • 없음

    [보고서]

    • 없음

    [URL]

    • 없음

     

     문의사항

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

    • sangho.lee.1990@gmail.com

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

    • saimang0804@gmail.com

     

     

     

     

     

     

     

     

     

     

     

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