Lecture 2. Shell Tools and Scripting

Park Choong Ho·2021년 5월 12일
0

Missing Semester

목록 보기
2/2

Shell Tools and Scripting

Shell Scripting

지금까지 shell에서의 여러 명령어와 이 명령어들을 어떻게 연결시키는지에 대해서 공부했습니다. 하지만 이런 명령어 뿐만이 아니라 조건문과 반복문 같은 control flow를 제어가 필요한 여러가지 경우가 있습니다.

쉘 스크립트는 이런 경우에 필요한 요소입니다. 대부분 쉘은 변수, control flow, 자신만의 문법을 가진 자신만의 scripting 언어를 가지고 있습니다. 쉘 스크립트 언어가 다른 스크립트 언어와 다른점은 쉘 스크립트는 쉘 관련된 일을 수행하는데 최적화되어 있다는 점입니다. 따라서 명령어 파이프라인 만들기, 결과를 파일에 저장, 표준 입력으로 부터 읽어들이기 같은 작업들은 쉘 스크립트에서 기본적인 작업입니다. 우리는 여기서 bash scripting에 초점을 맞추도록 하겠습니다.(그 이유는 bash scripting이 가장 널리 알려져 있기 때문입니다.)

bash에 변수를 할당하기 위해서는 foo=bar 문법을 사용하고 $foo를 통해서 변수 값에 접근합니다. foo = bar는 동작하지 않는다는 것을 염두하시기 바랍니다. 왜냐하면 위 코드는 foo라는 프로그램을 =, bar 두 argument를 주고 실행하는 것으로 해석되기 때문입니다. 대개, 쉘 스크립트에서 space 문자는 argument를 구분하는데 사용됩니다. 헷갈리기 쉬운 부분이므로 항상 확인해야 합니다.

bash에서 string은 ' 또는 "로 정의할 수 있습니다. 하지만 이둘은 차이점이 있는데 '은 literal string으로 변수를 대체하지 않는데 반해 "은 변수를 대체합니다.

foo=bar

echo "$foo"
# prints bar

echo '$foo'
#prints $foo

다른 프로그래밍 언어처럼, bash는 프로그램 흐름을 제어할 수 있는 요소들(if, case, while, for)을 제공합니다. 유사하게, bash는 argument를 가져가서 동작하는 함수도 가지고 있습니다.

mcd(){
	mkdir -p "$1"
	cd "$1"
}

여기서 $1은 script/function의 첫번째 인자입니다. 다른 스크립트 언어와 다르게 bash는 argument, error code, 다른 연관성 있는 변수들을 가리키는 특별한 변수를 사용합니다. 아래가 그 예시들이고 다 자세한 설명은 링크에 있습니다.

  • $0 script의 이름
  • $1 to $9 script arguement, $1이 1번째 argument
  • $@ 모든 argument들
  • $# argument 갯수
  • $? 전 명령어의 반환 코드
  • $$ 현재 스크립트의 PID(Process Identification Number)
  • !! argument를 포함한 직전 명령어 전체. 가장 전형적인 패턴은 missing permission으로 인한 명령어 실행이 실패한 경우에 다시 해당 명령어를 실행할 때 사용. sudo !!를 통해 직전 명령어를 sudo로 실행할 수 있습니다.
  • $_ 직전 명령어의 마지막 argument. interactive shell에서는 ESC key 다음 . key를 눌러서 가져올 수도 있습니다.

명령어들은 결과는 STDOUT, 에러는 STDERR를 사용해서 반환합니다. Return Code는 좀더 스크립트에 적합한 방식으로 에러를 알리기 위해 사용합니다. Return code 또는 exit status는 script/command가 어떻게 실행되었는지를 알리기 위한 방법입니다. 값 0이 모든 것이 올바르게 작동되었다는 뜻이고 0과 다른 값들은 어떤 에러가 발생했다는 의미입니다.

exit code는 상황에 따라서 명령어를 실행하기 위해 &&, ||같은 short-circuiting oprerator와 함께 사용되기도 합니다. 명령어들은 같은 라인에서 ;를 기준으로 분리됩니다. true 프로그램은 항상 return code 0을 가지고 false 명령어는 항상 return code 1을 가집니다. 몇가지 예시를 살펴보죠.

false || echo "Oops, fail"
# Oops, fail

true || echo "Will not be printed"
#

true && echo "Things went well"
# Things went well

false && echo "Will not be printed"
#

true ; echo "This will always run"
# This will always run

false ; echo "This will always run"
# This will always run

또다른 패턴은 명령어의 결과를 변수로써 가지는 것입니다. command substitution을 활용하면 가능합니다. $(CMD)를 입력하면 CMD를 실행하고 그 자리에 결과값이 자리하게 됩니다. 예를 들어, for file in $(ls) 를 치면 shell은 먼저 ls 를 실행하고 그 결과 값을 순환할 것입니다. 비슷한 것으로 process substitution이 있습니다. < ( CMD )CMD를 실행하고 그 결과를 특정파일에 넣고 < ()에 자리한 파일이름으로 대체할 것입니다. 명령어가 STDIN대신 파일이 필요한 경우에 유용합니다. 예를 들어 diff <(ls foo) <ls(bar)foo, bar 두 폴더 간의 차이점을 보여줄 것 입니다.

한가지 예시를 살펴보도록 하겠습니다.

#!/bin/bash

echo "Starting program at $(date)" # Date will be substituted

echo "Running program $0 with $# arguments with pid $$"

for file in "$@"; do
    grep foobar "$file" > /dev/null 2> /dev/null
    # When pattern is not found, grep has exit status 1
    # We redirect STDOUT and STDERR to a null register since we do not care about them
    if [[ $? -ne 0 ]]; then
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

$?가 0과 같은지 확인하는 절차가 있었습니다. Bash는 이러한 형태의 비교를 실행합니다. Bash에서 비교를 실행할 때는, double bracket [[]]을 사용하는 것을 권장합니다. 실수할 가능성이 줄어들기 떄문입니다. (이유는 더 자세히 공부해봐야 알듯....)

script를 실행할때, 우리는 종종 비슷한 형태의 argument를 주는 경우가 있습니다. Bash는 filename extension을 활용해 더 쉽고 확장성 있는 expression을 만드는 방법을 가지고 있습니다. 이런 기술들을 shell globbing이라고 합니다.

  • Wildcards: wildcard matching을 할 때마다 ?*을 활용해 하나 또는 여러개의 문자와 각각 맞는지 확인할 수 있습니다. 예를 들어, foo, foo1, foo2, foo10, bar 파일이 있다고 했을 때, rm foo?명령어는 foo1foo2를 삭제하고 rm foo*bar파일을 제외한 모든 파일을 삭제할 것입니다.
  • Curly braces {}: 명령어상에서 substring을 가지고 있는 경우에 curly braces를 써서 자동으로 확장할 수 있습니다. 파일을 옮기거나 변환할 때 유용한 방법입니다.
convert image.{png,jpg}
# Will expand to
convert image.png image.jpg

cp /path/to/project/{foo,bar,baz}.sh /newpath
# Will expand to
cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath

# Globbing techniques can also be combined
mv *{.py,.sh} folder
# Will move all *.py and *.sh files


mkdir foo bar
# This creates files foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h
touch {foo,bar}/{a..h}
touch foo/x bar/y
# Show differences between files in foo and bar
diff <(ls foo) <(ls bar)
# Outputs
# < x
# ---
# > y

bashscript를 작성하는 것은 어렵과 비직관적입니다. shellcheck 같은 도구들은 sh/bash script의 에러를 찾는 것을 도와줍니다.

script가 항상 bash로 작성될 필요는 없습니다. 예를 들어 python script를 활용하여 argument를 뒤집어진 순서로 보여줄 수도 있습니다.

#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

shebang을 script 맨 윗줄에 넣음으로써 kernel은 해당 스크립트를 shell command가 아닌 python 인터프리터로 실행하는 것을 알 수 있습니다. shebang을 env 명령어를 활용해 해당 명령이 system 어디에 위치해 있는지 적는것은 스크립트의 portability를 높이는 좋은 습관입니다. 위치를 알기위해서, envPATH라는 환경변수를 사용합니다. 이 예시에서 shebang line은 #! /usr/bin/env python 처럼 보입니다.

여기서 우리가 알고 있어야할 쉘함수와 쉘스크립트의 차이를 짚고 넘어가봅시다.

  • 함수는 쉘과 같은 언어입니다. 반면에, 스크립트는 다른 그 어떤 언어로도 작성될 수 있습니다. 이것이 바로 스크립트에서늬 shebang이 중요한 이유입니다.
  • 함수 정의가 읽어질 때 함수는 한번 로드됩니다. 스크립트는 실행될때마다 로드됩니다. 이러한 특징은 함수가 더빨리 로드될 수 있게 해주지만, 함수가 변경될떄마다 다시 로드해주어야합니다.
  • 함수들은 현재 쉘환경에서 실행되는 반면 스크립트는 자신만의 프로세스안에서 실행됩니다. 따라서 함수는 환경변수들(예를 들어, 현재 폴더등)을 변경할 수 있는 반면에, 스크립트는 그럴 수 없습니다. 스크릅트들은 export를 활용해 노출된 환경변수들을 받게됩니다.
  • 어떤 프로그래밍 언어에 있어서든, 함수들은 모듈성, 코드 재사용성, 쉘코드의 명확성을 얻을 수 있는 강력한 구조입니다. 쉘 스크립트는 종종 자신이 정의한 함수를 포함하기도 합니다.

Shell Tools

Finding how to use commands

이시점에서, 아마도 alias관련 명령어 플래그들을 어떻게 찾는지 궁금할 수 있습니다.(예를 들어, ls -l, mv -i, mkdir -p) 일반적으로, 어떤 명령어가 주어졌을 때 어떻게 이 명령어 사용법과 서로 다른 옵션들을 찾아내나요? 아마도 구글링을 할텐데, UNIX는 StackOverFlow보다 먼저 존재했기에, 이러한 정보를 얹는 built-in 방법이 있습니다.

shell 강의에서도 보았듯이, 첫번째 방법은 명령어를 -h 또는 --help flag와 같이 입력하는 것입니다. 더 상세한 방법은 man 명령어를 사용하는 것입니다. 메뉴얼을 간략하게 보기위해, man명령어는 manual page를 제공합니다. man rm명령어는 rm 명령어의 동작을 보여줍니다(플래그도 포함합니다.). 사실, 지금까지 제가 모든 명령어와 연결해드린 모든 것은 명령어들에대한 리눅스 메뉴얼 페이지의 온라인 버젼입니다. 심지어 non-native한 명령어들도 manpage entries를 가지고 있습니다.

Finding Files

모든 프로그래머가 마주하는 반복적인 일중 하나는 바로 파일이나 폴더를 찾는 것입니다. 모든 유닉스 기반 시스템들은 find라는 파일을 찾아주는 위대한 shell tool을 내장하고 있습니다. find는 재귀적으로 특정 criteria와 맞아떨어지는 파일들을 찾습니다.

profile
백엔드 개발자 디디라고합니다.

0개의 댓글