트랙
/
Bash
Bash
/
학습 과정
/
파이프라인과 명령 목록
파이

파이프라인과 명령 목록 에서 Bash

{other: "%{count}개의 연습 문제"}

파이프라인과 명령 목록 소개

우리는 명령 뒤에 인자가 오는, 간단한 명령을 작성하는 방법을 이미 살펴봤어요. 이제 간단한 명령을 조합해서 더 복잡한 명령을 만드는 방법을 알아볼게요.

입력과 출력

시작하기 전에, 입력과 출력에 대해 간단히 알아볼까요?

프로세스에는 "표준 입출력 채널"이 있어요.

  • 프로세스는 "stdin"에서 _입력_을 받을 수 있어요.
  • 프로세스는 "stdout"으로 _출력_을 내보낼 수 있어요.
  • 프로세스는 "stderr"로 _오류 출력_을 내보낼 수 있어요.

tr 명령이 이것의 아주 순수한 예예요. 이 명령은 입력에서 텍스트를 읽고, 문자를 변환한 다음, 그 결과 텍스트를 출력으로 내보낼 뿐이에요.

"stdio" 채널을 다루는 방법은 나중에 교육 과정에서 더 자세히 살펴볼게요.

파이프라인

이것은 셸 프로그래밍의 대표적인 강점 중 하나예요. 파이프라인을 사용하면 텍스트 스트림에 정교한 변환을 적용할 수 있어요.

정렬된 사용자 목록을 만들려면:

cat /etc/passwd | cut -d : -f 1 | sort

파이프 기호(|)는 한 명령의 출력을 다른 명령의 입력에 연결해요. cut은 cat의 출력을 읽고, sort는 cut의 출력을 읽어요.

Advanced
  • 기본적으로 파이프라인의 각 명령은 별도의 서브셸에서 실행돼요. (서브셸은 현재 실행 중인 셸을 복사한 자식 프로세스예요.)

  • 파이프라인을 실행하면 성능 비용이 들어요. 비슷한 명령으로 이루어진 긴 파이프라인을 만들고 있다면, 하나의 명령으로 합치는 것을 고려해 봐요. 예를 들어 grep, cut, sed, awk, tr을 여러 번 사용하는 파이프라인은 보통 하나의 awk 명령으로 합쳐서 더 효율적으로 만들 수 있어요.

  • 파이프라인의 종료 상태는 파이프라인에서 마지막 명령의 종료 상태예요. 즉, 앞의 명령이 실패했더라도 마지막 명령이 성공하면 파이프라인 전체의 종료 상태는 0이에요.

    이 동작을 바꿀 수 있는 셸 설정이 있어요. "pipefail" 설정을 (set -o pipefail로) 켜면 파이프라인의 종료 상태는 다음과 같아요:

    • 모든 명령이 성공하면 0, 그렇지 않으면
    • 파이프라인에 있는 명령 중 마지막 0이 아닌 종료 상태.

명령 목록

명령 목록은 ;(또는 줄바꿈), &&, ||로 구분된 파이프라인의 나열이에요.

  • A; B는 A가 끝난 뒤에 B가 실행되는 명령 목록이에요.
  • A && B는 A가 성공(종료 상태 0)했을 때만 B가 실행돼요.
  • A || B는 A가 실패(종료 상태가 0이 아닐 때)했을 때만 B가 실행돼요.

명령 목록의 종료 상태는 마지막으로 실행된 명령의 종료 상태예요.

&&와 || 연산자는 이어서 연결할 수 있어요. 그러면 다음 명령이 앞선 명령들의 상태에 따라 조건부로 실행돼요. 예를 들어

A && B && C && D || E
  • A가 성공하면 B가 실행되고,
  • A와 B가 성공하면 C가 실행되고,
  • A, B, C가 모두 성공하면 D가 실행되고,
  • A, B, C, D 중 어느 하나라도 실패하면 E가 실행돼요.
Caution

이 논리 연산자들은 아껴서 사용해요. 금방 읽기 어려운 코드나 이해하기 힘든 로직으로 이어질 수 있어요.

예를 들어, 다음 두 가지가 같다고 생각할 수 있어요:

if A; then B; else C; fi
A && B || C

차이는 바로, C가 언제 실행되느냐예요.

  • 첫 번째 코드(if문)에서는 A가 실패할 때만 C가 실행돼요.
  • 두 번째 코드에서는 A가 실패하거나 A가 성공했지만 B가 실패하면 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")
Note

_줄 이어쓰기_는 "백슬래시"와 "줄바꿈"이라는 두 문자로 이루어진 시퀀스예요(\ + \n). Bash가 이 시퀀스를 만나면 코드에서 그냥 제거하고, 그렇게 해서 현재 줄을 다음 줄로 이어가요. 백슬래시와 줄바꿈 사이에 공백이 들어가지 않도록 주의해요.

또 다른 예를 볼까요

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

또는

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
GitHub에서 편집 링크가 새 창이나 탭에서 열려요