우리는 명령 뒤에 인자가 오는, 간단한 명령을 작성하는 방법을 이미 살펴봤어요. 이제 간단한 명령을 조합해서 더 복잡한 명령을 만드는 방법을 알아볼게요.
시작하기 전에, 입력과 출력에 대해 간단히 알아볼까요?
프로세스에는 "표준 입출력 채널"이 있어요.
tr 명령이 이것의 아주 순수한 예예요.
이 명령은 입력에서 텍스트를 읽고, 문자를 변환한 다음, 그 결과 텍스트를 출력으로 내보낼 뿐이에요.
"stdio" 채널을 다루는 방법은 나중에 교육 과정에서 더 자세히 살펴볼게요.
이것은 셸 프로그래밍의 대표적인 강점 중 하나예요. 파이프라인을 사용하면 텍스트 스트림에 정교한 변환을 적용할 수 있어요.
정렬된 사용자 목록을 만들려면:
cat /etc/passwd | cut -d : -f 1 | sort
파이프 기호(|)는 한 명령의 출력을 다른 명령의 입력에 연결해요.
cut은 cat의 출력을 읽고, sort는 cut의 출력을 읽어요.
기본적으로 파이프라인의 각 명령은 별도의 서브셸에서 실행돼요. (서브셸은 현재 실행 중인 셸을 복사한 자식 프로세스예요.)
파이프라인을 실행하면 성능 비용이 들어요.
비슷한 명령으로 이루어진 긴 파이프라인을 만들고 있다면, 하나의 명령으로 합치는 것을 고려해 봐요.
예를 들어 grep, cut, sed, awk, tr을 여러 번 사용하는 파이프라인은 보통 하나의 awk 명령으로 합쳐서 더 효율적으로 만들 수 있어요.
파이프라인의 종료 상태는 파이프라인에서 마지막 명령의 종료 상태예요. 즉, 앞의 명령이 실패했더라도 마지막 명령이 성공하면 파이프라인 전체의 종료 상태는 0이에요.
이 동작을 바꿀 수 있는 셸 설정이 있어요.
"pipefail" 설정을 (set -o pipefail로) 켜면 파이프라인의 종료 상태는 다음과 같아요:
명령 목록은 ;(또는 줄바꿈), &&, ||로 구분된 파이프라인의 나열이에요.
A; B는 A가 끝난 뒤에 B가 실행되는 명령 목록이에요.A && B는 A가 성공(종료 상태 0)했을 때만 B가 실행돼요.A || B는 A가 실패(종료 상태가 0이 아닐 때)했을 때만 B가 실행돼요.명령 목록의 종료 상태는 마지막으로 실행된 명령의 종료 상태예요.
&&와 || 연산자는 이어서 연결할 수 있어요.
그러면 다음 명령이 앞선 명령들의 상태에 따라 조건부로 실행돼요.
예를 들어
A && B && C && D || E
이 논리 연산자들은 아껴서 사용해요. 금방 읽기 어려운 코드나 이해하기 힘든 로직으로 이어질 수 있어요.
예를 들어, 다음 두 가지가 같다고 생각할 수 있어요:
if A; then B; else C; fi
A && B || C
차이는 바로, C가 언제 실행되느냐예요.
if문)에서는 A가 실패할 때만 C가 실행돼요.명령 목록으로 코드를 더 간단하게 만들 수 있는 예를 몇 가지 살펴볼게요.
삼각형에 대한 데이터가 담긴 데이터 파일이 있고, 삼각형 하나가 세 변의 길이를 담은 세 개의 줄로 표현된다고 해봐요.
$ cat triangle.dat
3
4
5
9
12
14
조건이 세 개의 read 명령인 while문을 사용할 수 있어요:
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
is_pythagorean이 세 변이 피타고라스 방정식을 만족하는지 판별하는 명령이라고 하면, 출력은 이렇게 돼요:
3:4:5 is pythagorean
9:12:14 is not pythagorean
많은 프로그래밍 언어에는 어떤 조건이 실패하면 예외를 던지는 형태의 단언이 있어요
assert(user == "Administrator", "wrong user");
Bash에서는 OR 연산자를 사용해서 그런 기능을 흉내 낼 수 있어요:
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
긴 명령 목록은 금방 읽기 어려워져요. 줄바꿈을 적극적으로 활용하면 도움이 많이 돼요.
두 조건이 충족될 때 배열에 단어를 추가하는 다음 예를 살펴봐요.
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bash에서는 파이프나 논리 연산자 뒤에 줄바꿈을 넣을 수 있어요.
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
하지만 줄 끝에 있는 연산자는 놓치기 쉬워요. _줄 이어쓰기_를 사용하면 연산자를 줄 맨 앞에 둘 수 있어서, 목록이 이어지고 있다는 사실이 더 분명해져요:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
_줄 이어쓰기_는 "백슬래시"와 "줄바꿈"이라는 두 문자로 이루어진 시퀀스예요(\ + \n).
Bash가 이 시퀀스를 만나면 코드에서 그냥 제거하고, 그렇게 해서 현재 줄을 다음 줄로 이어가요.
백슬래시와 줄바꿈 사이에 공백이 들어가지 않도록 주의해요.
또 다른 예를 볼까요
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
또는
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort