Bash로 스크립트를 작성하다 보면 파일에서 데이터를 읽어오거나 파일에 데이터를 기록해야 하는 경우가 자주 생깁니다. 파일에는 설정 옵션이 담겨 있을 수도 있고, 사용자가 애플리케이션으로 생성한 데이터 자체일 수도 있습니다. 언어마다 이 작업을 처리하는 방식은 조금씩 다른데, 이 글에서는 Bash를 비롯한 POSIX 셸에서 데이터 파일을 다루는 방법을 소개합니다.
Bash 설치하기
Linux를 사용 중이라면 이미 Bash가 설치되어 있을 가능성이 높습니다. 만약 없다면 배포판의 소프트웨어 저장소에서 손쉽게 설치할 수 있습니다.
macOS에서는 기본 터미널을 그대로 사용하면 됩니다. macOS 버전에 따라 Bash 또는 Zsh가 제공됩니다.
Windows에서도 Bash를 사용하는 방법은 여러 가지입니다. Microsoft가 공식 지원하는 WSL(Windows Subsystem for Linux)을 활용하는 것이 대표적이며, Git Bash나 Cygwin 같은 도구도 좋은 선택지입니다.
Bash 설치를 마쳤다면 익숙한 텍스트 편집기를 열고 바로 코딩을 시작해 보세요.
Bash로 파일 읽기
Bash는 단순한 셸을 넘어 하나의 스크립팅 언어이기도 합니다. Bash에서 데이터를 읽어들이는 방법은 크게 두 가지입니다. 하나는 일종의 데이터 스트림을 만들어 출력을 파싱하는 방식이고, 다른 하나는 데이터를 메모리에 직접 불러오는 방식입니다. 두 방법 모두 유효하지만 각각의 용도가 뚜렷하게 구분되므로 상황에 맞게 선택해야 합니다.
source 명령으로 파일 불러오기
Bash에서 파일을 "소스(source)"한다는 것은 해당 파일의 내용이 Bash의 데이터 모델에 맞는 유효한 형식이라고 전제하고 읽어 들이는 것을 의미합니다. 아무 파일이나 소싱할 수는 없지만, 설정 파일이나 함수 정의를 읽어 올 때 매우 유용한 방법입니다.
예를 들어 example.sh라는 파일을 만들고 다음 내용을 입력해 보겠습니다:
#!/bin/sh
greet opensource.com
echo "The meaning of life is $var"
스크립트를 실행하면 오류가 발생합니다:
$ bash ./example.sh
./example.sh: line 3: greet: command not found
The meaning of life is
Bash에는 greet라는 명령이 없어서 해당 줄을 실행하지 못했고, var라는 변수에 대한 정보도 없으므로 삶의 의미 역시 비어 있는 상태로 출력됩니다. 이 문제를 해결하려면 include.sh라는 파일을 새로 만들어 보겠습니다:
greet() {
echo "Hello ${1}"
}
var=42
그다음 example.sh 스크립트에 source 명령을 추가합니다:
#!/bin/sh
source include.sh
greet opensource.com
echo "The meaning of life is $var"
스크립트를 다시 실행하면 정상적으로 동작합니다:
$ bash ./example.sh
Hello opensource.com
The meaning of life is 42
greet 명령이 include.sh 파일 안에 정의되어 있기 때문에 셸 환경으로 불려 들어왔고, 인자(opensource.com)까지 제대로 인식했습니다. 물론 var 변수도 함께 설정되어 임포트되었습니다. 이처럼 source는 코드 재사용과 설정 분리에 탁월한 도구입니다.
파일을 데이터 스트림으로 파싱하기
데이터를 Bash로 가져오는 또 다른 방법은 데이터 스트림으로 파싱하는 것입니다. 방법은 다양합니다. grep, cat처럼 데이터를 받아 stdout으로 파이핑하는 명령이라면 무엇이든 활용할 수 있습니다. 또는 Bash에 내장된 리디렉션 기능을 사용할 수도 있습니다. 리디렉션만으로는 큰 의미가 없기 때문에, 여기서는 내장 명령인 echo를 함께 사용해 결과를 출력해 보겠습니다:
#!/bin/sh
echo $( < include.sh )
이 코드를 stream.sh로 저장하고 실행하면 다음과 같은 결과를 볼 수 있습니다:
$ bash ./stream.sh
greet() { echo "Hello ${1}" } var=42
$
include.sh 파일의 각 줄이 Bash에 의해 터미널로 출력(echo)됩니다. 이렇게 읽어 들인 데이터를 적절한 파서에 파이핅하는 것이 Bash에서 데이터를 처리하는 일반적인 패턴입니다. 예를 들어 include.sh가 등호(=)로 구분된 키-값 쌍을 담은 설정 파일이라고 가정해 보겠습니다. 이 경우 awk나 cut을 이용해 원하는 값을 추출할 수 있습니다:
#!/bin/sh
myVar=`grep var include.sh | cut -d'=' -f2`
echo $myVar
스크립트를 실행하면 값만 깔끔하게 추출됩니다:
$ bash ./stream.sh
42
Bash로 파일에 데이터 쓰기
사용자가 애플리케이션으로 생성한 데이터를 저장하든, 사용자의 활동에 대한 메타데이터(예: 게임 세이브 파일이나 최근 재생한 곡 목록)를 기록하든, 나중에 다시 사용하기 위해 데이터를 저장할 이유는 무궁무진합니다. Bash에서는 흔히 쓰이는 셸 리디렉션만으로 파일에 데이터를 저장할 수 있습니다.
예를 들어 출력 내용으로 새 파일을 만들려면 단일 리디렉션 토큰을 사용합니다:
#!/bin/sh
TZ=UTC
date > date.txt
스크립트를 몇 번 실행해 보면 파일이 덮어쓰기되는 것을 확인할 수 있습니다:
$ bash ./date.sh
$ cat date.txt
Tue Feb 23 22:25:06 UTC 2021
$ bash ./date.sh
$ cat date.txt
Tue Feb 23 22:25:12 UTC 2021
반대로 기존 내용에 데이터를 추가(append)하려면 이중 리디렉션 토큰을 사용합니다:
#!/bin/sh
TZ=UTC
date >> date.txt
스크립트를 여러 번 실행하면 결과가 계속 누적됩니다:
$ bash ./date.sh
$ bash ./date.sh
$ bash ./date.sh
$ cat date.txt
Tue Feb 23 22:25:12 UTC 2021
Tue Feb 23 22:25:17 UTC 2021
Tue Feb 23 22:25:19 UTC 2021
Tue Feb 23 22:25:22 UTC 2021
단일 리디렉션(>)은 파일을 새로 생성하거나 기존 내용을 덮어쓰고, 이중 리디렉션(>>)은 파일 끝에 내용을 이어 붙인다는 점을 꼭 기억하세요.
배우기 쉬운 프로그래밍, Bash
Bash의 가장 큰 장점은 학습 곡선이 완만하다는 것입니다. 기본 개념 몇 가지만 익히면 복잡한 프로그램도 충분히 만들 수 있습니다. 더 자세한 내용이 궁금하다면 GNU.org에서 제공하는 공식 Bash 문서를 참고해 보세요.