배경

최근에 간단한 쉘 스크립트 하나를 짜면서 겪은 문제가 하나 있다. ssh로 연결된 특정 보드의 이더넷 네트워크 인터페이스의 설정을 변경해주는 작업이었다. 적용할 보드가 한두개가 아니라서 해당 작업을 간편하게 하기위해서 HOST(노트북)에서 해당 작업을 수행하는 스크립트를 실행시키면 ssh를 통해 보드내 설정을 바꿔주도록 했다. 딱히 어려울것 없는 작업이라 sshpass, scp, ssh, sed 명령어 조합을 사용하였다.

로컬 터미널에서 위의 명령어들을 쓰면서 잘 바뀌는것을 확인했고 그 로직을 그대로 쉘 스크립트를 작성하였다… 하지만 문제는 로컬 터미널에서만 테스트 한 점이었다.

# copy
scp -o StrictHostKeyChecking=no config.yaml user@1.1.1.1:~
# change
ssh -o StrictHostKeyChecking=no user@1.1.1.1 \
    "`export mac_addr=`cat /sys/class/net/eth0/address`` &&
     sed -i 's/mac_addr/'"${mac_addr}"'/g' ~/config.yaml"

내용은 대략 이러했다.

  • 템플릿 config 파일을 보드로 복사
  • 해당 보드의 Mac address를 환경변수로 저장
  • sed 명령어로 템플릿 config 파일의 특정 문자열을 치환

하지만 저 상태로 스크립트를 실행하면 ${mac_addr} 부분이 스크립트를 실행한 로컬 터미널의 환경변수를 참조하게 되므로 공백값이 들어가게된다. quote 문자를 제대로 쓰지못해서 일어난것임을 깨닫고 bash에서의 double, single quote의 차이점을 알아봤다.
(`,",‘의 쓰임새를 제대로 알지 못하고 중구난방으로 쓴것을 볼 수 있다..)

bash에서의 “, ’ 기능

그렇다면 Bash에서 double quote(")와 single quote(’)의 차이점은 무엇일까??
아주 간단한 예시가 있다.

$ echo "$(echo "hi")"
-> hi
$ echo '$(echo "hi")'
-> $(echo "hi")

보이는것처럼 double quote로 감싸진 문장은 $(echo "hi")가 명령어로 확장된다. 하지만 single quote로 감싸진 문장은 명령어로 확장되지않고, 문자열(literal value) 그 자체를 보존한다.
정확히는 double quote(")는 명령어의 결과를 변수로 저장할 때 보통 많이 쓰는 $,`, \ 문자를 제외한 모든 문자를 보존한다.

수정

따라서 위의 스크립트가 본래 목적을 수행하도록 특정 부분을 수정했다.

  • $(cat /sys/class/...) 부분이 명령어로 치환되지않고 문자열 그대로 전달되도록
  • sed -i 's/.../${MAC_ADDR}/g' 부분의 환경변수가 치환되지않고 그대로 전달되도록

결론

방법은 여러가지가 있다. 수행할 명령어들을 모두 로컬변수로 담아서 문자열 그대로 전달할수도 있고, '로 시작해서 &&를 이용해 여러 명령어를 붙여 한 명령어의 문자열로 모두 담아서 보내버릴수도 있다. 하지만 괜히 이상하게 오기가 생겨서 원래 하려던 방법으로 나름의 챌린징(삽질)을 했다..