많은 사람들이 유닉스 셸을 배우려는 가장 큰 이유 중 하나는 바로 일괄 처리(batch processing)의 강력한 기능을 활용하기 위해서입니다. 수많은 파일에 동일한 작업을 적용하고 싶다면, 그 파일들을 하나씩 순회하며 명령을 실행하는 방법이 필요합니다. 프로그래밍 용어로 이를 실행 제어(execution control)라고 부르며, 그중 가장 대표적인 예가 바로 for 루프입니다.
for 루프는 지정한 각 데이터 객체(예: 파일)에 대해 컴퓨터가 어떤 작업을 수행할지 상세히 정의한 일종의 '레시피'입니다.
클래식한 for 루프
가장 쉽게 시도해 볼 수 있는 루프는 파일 모음을 분석하는 것입니다. 이 루프 자체는 실용적이지 않을 수 있지만, 디렉터리 안의 각 파일을 개별적으로 다룰 수 있다는 사실을 스스로 확인하기에는 안전한 방법입니다. 먼저 테스트 환경을 만들기 위해 디렉터리를 생성하고 몇 개의 파일을 복사해 넣습니다. 처음에는 어떤 파일이든 상관없지만, 이후 예제에서는 이미지 파일(JPEG, PNG 등)이 필요합니다. 파일 관리자를 사용하거나 터미널에서 다음과 같이 폴더를 만들고 파일을 복사할 수 있습니다:
$ mkdir example
$ cp ~/Pictures/vacation/*.{png,jpg} example
새 폴더로 이동한 후 파일 목록을 확인하여 테스트 환경이 의도대로 준비되었는지 점검합니다:
$ cd example
$ ls -1
cat.jpg
design_maori.png
otago.jpg
waterfall.png
각 파일을 하나씩 순회하는 루프의 기본 문법은 다음과 같습니다. 먼저 변수를 만듭니다(예: file을 의미하는 f). 그런 다음 해당 변수가 순회할 데이터 집합을 정의합니다. 여기서는 * 와일드카드 문자를 사용해 현재 디렉터리의 모든 파일을 대상으로 합니다(*는 모든 것과 일치). 마지막으로 세미콜론(;)으로 이 도입부를 종료합니다.
$ for f in * ;취향에 따라 여기서 Return(Enter) 키를 눌러도 됩니다. 셸은 구문이 완전해질 때까지 루프를 실행하지 않습니다.
다음으로, 루프의 각 반복에서 어떤 작업을 수행할지 정의합니다. 간단하게 하기 위해 file 명령으로 각 파일에 대한 정보를 확인해 보겠습니다. 각 파일은 f 변수로 표현되며, 앞에 $를 붙여야 셸이 변수의 현재 값으로 치환합니다:
do file $f ;또 다른 세미콜론으로 절을 종료하고 루프를 닫습니다:
doneReturn 키를 누르면 셸이 현재 디렉터리의 모든 항목을 순회하기 시작합니다. for 루프는 각 파일을 하나씩 변수 f에 할당하며 명령을 실행합니다:
$ for f in * ; do
> file $f ;
> done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
다음과 같이 한 줄로 작성할 수도 있습니다:
$ for f in *; do file $f; done
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
여러 줄 형식과 한 줄 형식은 셸 입장에서 완전히 동일하며 결과도 같습니다.
실전 예제
루프가 일상적인 컴퓨팅에서 얼마나 유용한지 보여주는 실전 예제를 살펴보겠습니다. 친구들에게 보내고 싶은 휴가 사진이 많다고 가정해 봅시다. 사진 파일이 너무 커서 이메일로 보내기 어렵고, 사진 공유 서비스에 올리기도 번거롭습니다. 웹용 축소 버전을 만들고 싶지만 사진이 100장이라면 한 장씩 줄이는 데 시간을 낭비하고 싶지 않을 것입니다.
먼저 Linux, BSD 또는 Mac의 패키지 관리자를 사용해 ImageMagick을 설치합니다. Fedora나 RHEL에서는 다음과 같습니다:
$ sudo dnf install ImageMagickUbuntu나 Debian에서는 다음과 같습니다:
$ sudo apt install ImageMagickBSD에서는 ports나 pkgsrc를, Mac에서는 Homebrew나 MacPorts를 사용하세요.
ImageMagick을 설치하면 사진을 처리할 수 있는 새로운 명령어들이 생깁니다.
생성될 파일을 담을 목적지 디렉터리를 만듭니다:
$ mkdir tmp각 사진을 원본 크기의 33%로 줄이려면 다음 루프를 사용합니다:
$ for f in * ; do convert $f -scale 33% tmp/$f ; done그런 다음 tmp 폴더를 열어 축소된 사진들을 확인합니다.
루프 안에는 원하는 만큼 많은 명령을 사용할 수 있습니다. 따라서 여러 파일에 복잡한 작업을 수행해야 한다면 전체 워크플로우를 for 루프의 do와 done 사이에 배치하면 됩니다. 예를 들어 처리된 각 사진을 웹 호스트의 공유 사진 디렉터리로 바로 복사하고, 로컬 시스템에서는 임시 파일을 삭제하고 싶다면 다음과 같이 할 수 있습니다:
$ for f in * ; do
convert $f -scale 33% tmp/$f
scp -i seth_web tmp/$f seth@example.com:~/public_html
trash tmp/$f ;
done
for 루프가 처리하는 각 파일에 대해 컴퓨터가 세 가지 명령을 자동으로 실행합니다. 즉, 이 방식으로 단 10장의 사진만 처리해도 30개의 명령 입력을 절약하는 셈이며, 최소한 그만큼의 시간도 아낄 수 있습니다.
루프 범위 제한하기
루프가 항상 모든 파일을 대상으로 해야 하는 것은 아닙니다. 예제 디렉터리에서 JPEG 파일만 처리하고 싶다면 다음과 같이 하면 됩니다:
$ for f in *.jpg ; do convert $f -scale 33% tmp/$f ; done
$ ls -m tmp
cat.jpg, otago.jpg
파일이 아니라 특정 작업을 정해진 횟수만큼 반복해야 할 수도 있습니다. for 루프의 변수는 제공하는 데이터에 따라 정의되므로, 파일 대신 숫자를 순회하는 루프를 만들 수도 있습니다:
$ for n in {0..4}; do echo $n ; done
0
1
2
3
4더 알아보는 루프 활용
이제 직접 루프를 만들 수 있는 충분한 지식을 갖추셨습니다. 루프에 익숙해질 때까지는 처리하려는 파일의 복사본을 대상으로 연습하고, 가능한 한 내장된 안전 장치가 있는 명령을 사용하세요. 실수로 디렉터리 전체의 파일을 같은 이름으로 변경해 서로 덮어쓰는 것처럼 되돌릴 수 없는 오류를 방지할 수 있습니다.
고급 for 루프 주제를 더 알아보려면 계속 읽어보세요.
모든 셸이 Bash인 것은 아닙니다
for 키워드는 Bash 셸에 내장되어 있습니다. 비슷한 많은 셸이 동일한 키워드와 문법을 사용하지만, tcsh 같은 일부 셸은 foreach라는 다른 키워드를 사용합니다.
tcsh에서는 문법의 취지는 비슷하지만 Bash보다 엄격합니다. 아래 코드 예제에서 두 번째와 세 번째 줄의 foreach? 문자열은 직접 입력하는 것이 아니라, 루프 작성이 아직 진행 중임을 알려주는 보조 프롬프트입니다.
$ foreach f (*)
foreach? file $f
foreach? end
cat.jpg: JPEG image data, EXIF standard 2.2
design_maori.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
otago.jpg: JPEG image data, EXIF standard 2.2
waterfall.png: PNG image data, 4608 x 2592, 8-bit/color RGB, non-interlaced
tcsh에서는 foreach와 end가 반드시 각각 별도의 줄에 단독으로 위치해야 하므로, Bash와 유사한 셸에서처럼 한 줄로 루프를 작성할 수 없습니다.
find 명령어를 활용한 루프
이론적으로 for 루프 기능을 제공하지 않는 셸을 만날 수도 있고, 추가 기능이 있는 다른 명령을 선호할 수도 있습니다.
find 명령은 for 루프의 기능을 구현하는 또 다른 방법으로, 루프에 포함할 파일의 범위를 정의하는 여러 가지 방법과 병렬 처리 옵션까지 제공합니다.
find 명령은 하드 드라이브에서 파일을 찾도록 설계되었습니다. 문법은 간단합니다. 검색할 경로를 지정하면 find가 모든 파일과 디렉터리를 찾아줍니다:
$ find .
.
./cat.jpg
./design_maori.png
./otago.jpg
./waterfall.png
이름의 일부를 추가하면 검색 결과를 필터링할 수 있습니다:
$ find . -name "*jpg"
./cat.jpg
./otago.jpg
find의 훌륭한 점은 발견한 각 파일을 -exec 플래그를 통해 루프에 넘길 수 있다는 것입니다. 예를 들어 예제 디렉터리에서 PNG 사진만 축소하려면 다음과 같이 합니다:
$ find . -name "*png" -exec convert {} -scale 33% tmp/{} \;
$ ls -m tmp
design_maori.png, waterfall.png-exec 절에서 중괄호 {}는 find가 처리 중인 항목(즉, 발견된 PNG로 끝나는 각 파일)을 나타냅니다. -exec 절은 세미콜론으로 종료해야 하지만, Bash는 보통 세미콜론을 자신의 용도로 사용하려 합니다. 백슬래시(\;)로 세미콜론을 '이스케이프'하면 find가 그 세미콜론을 자신의 종료 문자로 인식합니다.
find 명령은 자신의 역할에 매우 능숙해서, 때로는 지나치게 잘 작동할 때도 있습니다. 예를 들어 다른 사진 처리 작업을 위해 다시 PNG 파일을 검색하면 몇 가지 오류가 발생할 수 있습니다:
$ find . -name "*png" -exec convert {} -flip -flop tmp/{} \;
convert: unable to open image `tmp/./tmp/design_maori.png':
No such file or directory @ error/blob.c/OpenBlob/2643.
...find가 현재 디렉터리(.)의 PNG 파일뿐 아니라 이전에 처리해서 tmp 하위 디렉터리에 넣어둔 파일까지 모두 찾아낸 것입니다. 경우에 따라 find가 현재 디렉터리와 그 안의 모든 하위 디렉터리(그리고 그 안의 디렉터리까지)를 검색하기를 원할 수 있습니다. 이는 복잡한 파일 구조(예: 음악 아티스트 디렉터리 안에 앨범 디렉터리가 있고 그 안에 음악 파일이 있는 구조)에서 강력한 재귀 처리 도구가 될 수 있지만, -maxdepth 옵션으로 검색 깊이를 제한할 수 있습니다.
현재 디렉터리의 PNG 파일만 찾으려면(하위 디렉터리 제외):
$ find . -maxdepth 1 -name "*png"현재 디렉터리와 한 단계 아래 하위 디렉터리까지 검색하려면 최대 깊이를 1 증가시킵니다:
$ find . -maxdepth 2 -name "*png"기본값은 모든 하위 디렉터리를 탐색하는 것입니다.
즐겁고 실용적인 루프 활용
루프를 많이 사용할수록 시간과 노력이 절약되고, 감당할 수 있는 작업의 규모도 커집니다. 여러분은 한 명의 사용자일 뿐이지만, 잘 설계된 루프를 활용하면 컴퓨터에게 힘든 일을 맡길 수 있습니다.
루프는 다른 명령어처럼 다루어야 하며, 여러 파일에 동일한 작업을 반복해야 할 때 곧바로 꺼내 쓸 수 있도록 익혀두는 것이 좋습니다. 동시에 루프는 본격적인 프로그래밍으로 이어지는 확실한 관문이기도 합니다. 여러 파일에 복잡한 작업을 수행해야 한다면 잠시 시간을 내어 워크플로우를 계획해 보세요. 한 파일에서 목표를 달성할 수 있다면, 그 반복 가능한 과정을 for 루프로 감싸는 것은 비교적 간단합니다. 필요한 '프로그래밍'은 변수가 작동하는 방식을 이해하는 것과, 미처리 파일과 처리된 파일을 구분해 정리하는 조직력 정도입니다. 조금만 연습하면 일반 Linux 사용자에서 루프를 작성할 줄 아는 Linux 사용자로 성장할 수 있습니다. 이제 컴퓨터가 여러분을 위해 일하도록 만들어 보세요!