AI 보조 개발시 안전 원칙 | |||
데이터 손실 방지를 위한 필수 체크리스트 | |||
원칙 | 설명 | 실행방법 | 위험도 |
---|---|---|---|
Dry Run 우선 | AI 생성 코드도 반드시 echo로 먼저 확인 | echo 명령으로 시뮬레이션 | 높음 |
백업 필수 | 중요한 파일 처리 전에는 백업 생성 | cp로 원본 보존 | 매우 높음 |
단계적 실행 | 전체를 한번에 실행하지 말고 작은 단위로 테스트 | 작은 파일셋으로 먼저 테스트 | 중간 |
결과 검증 | AI 도구가 완벽하지 않으므로 결과를 반드시 검증 | 출력 파일 내용 확인 | 중간 |
5 반복과 루프
반복적으로 명령어를 실행하게 함으로써 자동화를 통해서 루프는 생산성 향상에 핵심이 된다. 와일드카드와 탭 자동완성과 유사하게, 루프를 사용하면 타이핑 상당량(타이핑 실수)을 줄일 수 있다. 와일드카드와 탭 자동완성은 타이핑을 (타이핑 실수를) 줄이는 두가지 방법이고, 또 다른 방법은 쉘이 반복해서 특정 작업을 수행하게 하는 것이다.
앞서 Nelle은 파이프와 필터를 활용해 1,520개의 NENE 데이터 파일에서 품질 문제가 있는 파일들을 식별할 수 있었다. 이제 각 파일에 goostats
프로그램을 실행하여 통계 분석을 수행해야 한다.
만약 1,520개 파일을 수동으로 처리한다면:
- 1,520번의 개별 명령 실행 필요
- 파일당 5초씩 소요시 총 2시간 이상
- 타이핑 실수와 파일명 오타 위험
north-pacific-gyre/2012-07-03
디렉토리에서 NENE*[AB].txt
파일들을 처리해보자. 먼저 처리할 파일 개수를 확인한다.
$ ls NENE*[AB].txt | wc -l
1518
이제 간단한 예제부터 시작하여 루프의 기본 개념을 학습해보자.
5.1 for 루프 기본 구조
north-pacific-gyre/2012-07-03
디렉토리에서 몇 개 파일만 선택하여 기본적인 루프를 작성해보자. for
루프 내부 코드를 들여쓰는 것이 일반적인 관행이다. 들여쓰는 유일한 목적은 코드를 더 읽기 쉽게 하는 것 밖에 없다. for
루프를 실행하는데는 꼭 필요하지는 않다.
$ for filename in NENE01729A.txt NENE01729B.txt NENE01736A.txt
> do
> head -n 3 $filename # 루프 내부에 들여쓰기는 가독성에 도움을 준다.
> done
==> NENE01729A.txt <==
1.03150932862
1.44755225695
0.224455411571
==> NENE01729B.txt <==
0.646620295293
0.529201331561
1.03149902331
==> NENE01736A.txt <==
2.16404354503
0.306827436349
3.64238088859
for 루프는 다음과 같은 구조로 이루어져 있다.
- for: 루프 시작을 알리는 키워드
- variable: 각 반복에서 현재 값을 저장할 변수명
- in: 변수와 리스트를 연결하는 키워드
- list_or_pattern: 처리할 파일명이나 값들의 목록 (와일드카드 사용 가능)
- do: 루프 본문 시작을 알리는 키워드
- commands: 각 반복에서 실행할 명령어들 (
$variable
로 현재 값 참조) - done: 루프 끝을 알리는 키워드
쉘이 키워드 for
를 보게 되면, 쉘은 리스트에 있는 각각에 대해 명령문 하나(혹은 명령문 집합)을 반복할 것이라는 것을 알게 된다. 루프를 반복할 때마다(iteration 이라고도 한다), 현재 작업하고 있는 파일 이름은 filename
으로 불리는 변수(variable)에 할당된다. 리스트의 다음 원소로 넘어가기 전에 루프 내부 명령어가 실행된다. 루프 내부에서, 변수 이름 앞에 $
기호를 붙여 변수 값을 얻는다: $
기호는 쉘 해석기가 변수명을 텍스트나 외부 명령어가 아닌 변수로 처리해서 값을 해당 위치에 치환하도록 지시한다.
이번 경우에 리스트는 파일이름이 3개다: NENE01729A.txt
, NENE01729B.txt
, NENE01736A.txt
. 매번 루프가 돌 때마다 파일명을 filename
변수에 할당하고 head
명령어를 실행시킨다. 각 NENE 파일은 북태평양 환류(North Pacific Gyre) 연구에서 측정한 단백질 농도 데이터를 담고 있다.
변수명을 분명히 구분하는데, 중괄호 내부에 변수명을 넣어서 변수로 사용하는 것도 가능하다: $filename
은 ${filename}
와 동치지만, ${file}name
와는 다르다.
for 루프를 입력할 때 쉘 프롬프트가 $
에서 >
으로 바뀐다. 이는 명령어가 아직 완성되지 않았음을 나타내는 연속 프롬프트다.
한 줄로 입력하려면 세미콜론 ;
을 사용하여 여러 명령어를 연결할 수 있다.
5.2 와일드카드와 루프
쉘은 *.txt
같은 와일드카드 패턴을 확장하여 매칭되는 파일 목록을 생성한다. 좀 더 복잡한 루프 예제를 살펴보자.
$ for filename in NENE*[AB].txt
> do
> echo $filename
> head -n 100 $filename | tail -n 20
> done
루프 본문은 각 파일에 대해 두 개의 명령어를 실행한다. 첫 번째 echo
명령어는 현재 처리 중인 파일명을 출력한다.
두 번째로, head
와 tail
을 조합한 파이프라인이 각 파일의 81-100번째 줄(총 20줄)을 추출하여 출력한다. head -n 100
으로 처음 100줄을 가져온 후, tail -n 20
으로 그 중 마지막 20줄만 선택하는 방식이다.
5.2.1 루프 내부 변수
루프에서 변수가 어떻게 작동하는지 이해하기 위해 두 가지 다른 접근 방식을 살펴보자. 다음은 겉보기에는 비슷해 보이지만 실제로는 매우 다른 결과를 생성하는 두 개의 루프다.
첫 번째 루프:
$ for datafile in NENE*[AB].txt
> do
> ls NENE*[AB].txt
> done
두 번째 루프:
$ for datafile in NENE*[AB].txt
> do
> ls $datafile
> done
첫 번째 루프는 매번 동일한 결과를 출력한다. ls NENE*[AB].txt
명령에서 Bash가 와일드카드 패턴을 확장하여 NENE01729A.txt
, NENE01729B.txt
, NENE01736A.txt
세 파일을 모두 나열하기 때문이다. 루프 변수 $datafile
을 전혀 사용하지 않고, 매번 와일드카드 패턴을 직접 평가한다.
두 번째 루프는 매번 다른 파일 하나씩 출력한다. $datafile
변수가 루프의 각 반복에서 현재 처리 중인 파일명으로 치환되어, ls
명령이 해당 파일만 출력한다. 이것이 루프 변수의 올바른 사용법이다.
이 차이점은 루프에서 변수를 제대로 활용하는 것이 얼마나 중요한지를 보여준다. 첫 번째는 와일드카드를 직접 사용해 매번 모든 파일을 나열하고, 두 번째는 루프 변수를 사용해 한 번에 하나씩 처리하는 것이 핵심적인 차이다.
5.2.2 파일명과 공백 처리
for 루프에서는 공백을 사용하여 리스트의 각 항목을 구분한다. 만약 파일명에 공백이 포함되어 있다면, 해당 파일명을 따옴표로 묶어야 한다.
$ for filename in "NENE 01729A.txt" "NENE 01729B.txt"
> do
> head -n 100 "$filename" | tail -n 3
> done
가능하다면 파일명에 공백이나 특수문자를 사용하지 않는 것이 좋다. 위 루프에서 $filename
주변의 따옴표를 제거하면 어떤 일이 일어나는지 실험해보자.
>
기호는 쉘의 연속 프롬프트로 사용되지만, 출력을 리다이렉션할 때도 사용된다. 마찬가지로 $
기호는 쉘 프롬프트에서 사용되지만, 변수의 값을 참조할 때도 사용된다.
5.3 Nelle 위기: 백업 중요성
북태평양 환류 연구를 진행 중인 Nelle은 지난주에 큰 실수를 저질렀다. 1,520개의 소중한 데이터 파일 중 일부에 잘못된 처리 스크립트를 실행해서 원본 데이터를 덮어써 버린 것이다. 다행히 백업이 있어서 복구할 수 있었지만, 이 경험으로 백업의 중요성을 뼈저리게 깨달았다.
이제 Nelle은 어떤 데이터 처리 작업을 하기 전에도 반드시 원본 파일의 백업 사본을 만들기로 결심했다. 하지만 1,520개 파일을 일일이 복사하는 것은 비현실적이다. 다행히 for 루프를 사용하면 이 작업을 자동화할 수 있다.
5.3.1 백업 파일 생성 자동화
원본 파일을 백업하는 문제로 돌아가서, for 루프를 사용하여 해결해보자.
$ for filename in NENE*[AB].txt
> do
> cp $filename original-$filename
> done
이 루프는 각 파일에 대해 cp
명령을 실행한다. 예를 들어 $filename
이 NENE01729A.txt
일 때, 쉘은 다음 명령을 실행한다.
cp NENE01729A.txt original-NENE01729A.txt
cp
명령은 성공 시 아무런 출력을 하지 않기 때문에, 루프가 올바르게 실행되고 있는지 확인하기 어렵다. 더욱이 Nelle은 지난주의 실수 때문에 더욱 조심스럽다. “혹시 또 실수를 하면 어떻게 하지?”라는 걱정이 든다.
5.3.2 시험 실행
Nelle과 같은 상황을 피하기 위해, 실제 작업을 수행하기 전에 echo
를 사용하여 실행될 명령어들을 미리 확인하는 것이 좋다. 이는 특히 대량의 파일을 처리할 때 필수적인 안전 장치다.
$ for filename in NENE*[AB].txt
> do
> echo "cp $filename original-$filename"
> done
이 방법으로 모든 명령어가 올바르게 생성되는지 확인한 후 실제 작업을 수행할 수 있다.
5.3.3 루프에서 리다이렉션 함정
루프에서 데이터를 파일로 저장할 때 주의할 점이 있다. 다음 루프를 살펴보자.
$ for datafile in NENE*[AB].txt
> do
> echo $datafile
> cat $datafile > processed_data.txt
> done
이 루프를 실행하면 각 반복에서 현재 파일의 내용이 processed_data.txt
에 저장된다. 하지만 >
연산자는 파일을 덮어쓰기 때문에, 루프가 완료된 후에는 마지막으로 처리된 파일의 내용만 남게 된다. 결과적으로 processed_data.txt
에는 NENE01736A.txt
의 내용만 저장된다.
만약 모든 파일의 내용을 하나로 합치려면 >>
연산자를 사용해야 한다.
$ for datafile in NENE*[AB].txt
> do
> cat $datafile >> all_data.txt
> done
>>
연산자는 파일에 내용을 추가하므로, 모든 NENE 파일의 내용이 순서대로 all_data.txt
에 합쳐진다. 이처럼 리다이렉션 연산자의 차이를 이해하는 것이 중요하다.
5.3.4 Dry Run 소중한 교훈
루프는 한 번에 많은 작업을 수행하는 강력한 도구다. 하지만 Nelle이 경험했듯이, 만약 잘못된 명령어가 있다면 동일한 실수가 수백 개 파일에 대해 순식간에 반복될 수 있다. “그때 Dry Run을 했더라면…”이라며 후회했던 Nelle의 경험을 통해 Dry Run(시험 실행) 기법의 중요성을 배워보자.
Dry Run은 시험 실행으로 실제 명령 대신 echo
명령을 사용하여 실행될 명령어들을 미리 확인하는 방법이다. 다음 예시로 살펴보자.
# 실제 실행하려는 루프
$ for file in NENE*[AB].txt
> do
> bash goostats $file analyzed-$file
> done
# Dry Run 버전
$ for file in NENE*[AB].txt
> do
> echo "bash goostats $file analyzed-$file"
> done
Dry Run에서는 따옴표로 전체 명령어를 묶는 것이 중요하다. 만약 다음과 같이 작성하면 예상했던 결과를 얻을 수 없다.
# 잘못된 방법
$ for file in NENE*[AB].txt
> do
> echo bash goostats $file > analyzed-$file
> done
이 경우 echo bash goostats $file
명령의 출력이 analyzed-$file
에 리다이렉션되어, 실제로 analyzed-NENE01729A.txt
, analyzed-NENE01729B.txt
등의 파일들이 생성된다. 이는 Dry Run의 목적에 맞지 않는다.
올바른 Dry Run은 따옴표를 사용하여 실행될 명령어를 그대로 화면에 표시하는 것이다. 이를 통해 실제 실행 전에 모든 명령어가 올바르게 생성되는지 검증할 수 있다.
5.3.5 중첩 루프와 패턴 매칭
백업 파일 생성에 성공한 Nelle은 점점 자신감을 얻어가고 있다. 이제 더 복잡한 작업에 도전해보려고 한다. 복잡한 루프 구조에서는 Dry Run이 더욱 중요하다는 것을 이미 배웠기 때문에, 이번에는 처음부터 신중하게 접근한다.
여러 측정소와 다양한 날짜의 데이터를 분석하는 프로젝트를 예로 들어보자. 다음과 같은 중첩 루프로 디렉토리 구조를 만들 수 있다:
# 먼저 Dry Run으로 확인
$ for station in NENE KANE OAHU
> do
> for date in 2012-07-03 2012-07-04 2012-07-05
> do
> echo "mkdir $station-$date"
> done
> done
이 중첩 루프(루프 안에 또 다른 루프가 있는 구조)를 실행하면 외부 루프는 각 측정소를, 내부 루프는 각 날짜를 반복하여 총 9개의 디렉토리가 생성된다: NENE-2012-07-03
, NENE-2012-07-04
, NENE-2012-07-05
, KANE-2012-07-03
, KANE-2012-07-04
, KANE-2012-07-05
, OAHU-2012-07-03
, OAHU-2012-07-04
, OAHU-2012-07-05
.
Dry Run으로 결과를 확인한 후 실제 명령을 실행한다.
# 실제 실행
$ for station in NENE KANE OAHU
> do
> for date in 2012-07-03 2012-07-04 2012-07-05
> do
> mkdir $station-$date
> done
> done
5.3.6 와일드카드 패턴 매칭
파일 선택에서도 와일드카드 패턴을 정확히 이해하는 것이 중요하다. 다음 루프를 살펴보자:
$ for filename in NENE01*
> do
> ls $filename
> done
이 루프에서 NENE01*
패턴은 NENE01
로 시작하는 모든 파일과 매치된다. 와일드카드 *
는 0개 이상의 문자를 나타내므로, NENE01729A.txt
, NENE01729B.txt
, NENE01736A.txt
, NENE01751A.txt
등이 매치된다. 이처럼 패턴 매칭을 통해 원하는 파일들만 선택적으로 처리할 수 있다.
이전 작업을 반복하는 또 다른 방법은 history
명령어를 사용하는 것이다. 최근에 실행된 수백 개의 명령어 목록을 확인한 후, !123
형태로 특정 명령어를 다시 실행할 수 있다(여기서 123은 명령어 번호). 예를 들어 Nelle이 다음과 같이 입력했다면:
$ history | tail -n 5
456 ls -l NENE0*.txt
457 rm stats-NENE01729B.txt.txt
458 bash goostats NENE01729B.txt stats-NENE01729B.txt
459 ls -l NENE0*.txt
460 history
그 후 단순히 !458
을 입력하면 NENE01729B.txt
파일에 대해 goostats
를 다시 실행할 수 있다.
또한, 명령 이력에 접근하는 편리한 단축키들도 있다.
Ctrl-R
: 역방향 검색 모드로 입력한 텍스트와 일치하는 가장 최근 명령어를 찾는다.!!
: 바로 직전에 실행한 명령어를 다시 실행한다.!$
: 이전 명령어의 마지막 인수(단어)를 가져온다.
백업 파일 생성을 마스터하고 Dry Run 기법까지 익힌 Nelle에게 드디어 진짜 도전이 찾아왔다. 지도교수로부터 북태평양 환류 연구의 핵심 데이터 1,518개 파일을 분석해달라는 요청을 받은 것이다.
이번에는 단순한 파일 복사(cp
)가 아닌, 각 파일마다 5초씩 걸리는 복잡한 통계 분석(goostats
)을 수행해야 한다. 수작업으로는 불가능한 이 작업을 for 루프로 어떻게 해결할까? 실패하면 소중한 연구 데이터를 잃을 수도 있는 중요한 순간이다.
5.4 Nelle 박사 1,518개 파일 정복
Nelle은 이제 goostats
프로그램(지도교수가 작성한 쉘 스크립트)을 사용하여 데이터 파일을 처리할 준비가 되었다. goostats
는 북태평양 환류에서 측정한 단백질 농도 데이터의 통계량을 계산하며, 두 개의 인수를 받는다.
- 입력 파일 (측정된 수치 데이터)
- 출력 파일 (계산된 통계량 저장)
이전의 백업 작업과는 차원이 다르다. 백업은 단순히 파일을 복사하는 것이었지만, 이번에는 각 파일을 복잡한 통계 프로그램으로 처리해야 한다. 1,518개 × 5초 = 약 2시간이 걸리는 대작업이다.
Nelle은 이전에 배운 Dry Run 기법을 활용하여 단계적으로 접근하기로 했다. 첫 번째 단계는 올바른 파일을 선택하는 것이다. ‘Z’가 아닌 ’A’ 또는 ’B’로 끝나는 파일이 유효한 데이터라는 점을 기억해야 한다. 홈 디렉토리에서 시작하여, Nelle이 조심스럽게 다음과 같이 입력한다.
$ cd north-pacific-gyre/2012-07-03
$ for datafile in NENE*[AB].txt
> do
> echo $datafile
> done
NENE01729A.txt
NENE01729B.txt
NENE01736A.txt
...
NENE02043A.txt
NENE02043B.txt
다음 단계는 goostats
분석 프로그램이 생성할 출력 파일의 이름을 정하는 것이다. 각 입력 파일에 “stats-” 접두사를 붙이는 것이 간단해 보이므로, 루프를 수정하여 확인해보자.
$ for datafile in NENE*[AB].txt
> do
> echo $datafile stats-$datafile
> done
NENE01729A.txt stats-NENE01729A.txt
NENE01729B.txt stats-NENE01729B.txt
NENE01736A.txt stats-NENE01736A.txt
...
NENE02043A.txt stats-NENE02043A.txt
NENE02043B.txt stats-NENE02043B.txt
goostats
를 아직 실행하지는 않았지만, 올바른 파일을 선택하고 적절한 출력 파일명을 생성할 수 있다는 것을 확인했다.
명령어를 반복해서 입력하는 것도 번거롭지만, 더 우려되는 것은 타이핑 실수다. 그래서 루프를 다시 입력하는 대신 위쪽 화살표 키를 눌러본다. 쉘은 전체 루프를 한 줄로 다시 표시해준다.
$ for datafile in NENE*[AB].txt; do echo $datafile stats-$datafile; done
왼쪽 화살표 키를 사용하여 커서를 이동하고, echo
명령어를 bash goostats
로 변경한다.
$ for datafile in NENE*[AB].txt; do bash goostats $datafile stats-$datafile; done
Enter 키를 누르면 쉘이 수정된 명령어를 실행한다. 그런데 아무 일도 일어나지 않는 것처럼 보인다. 출력이 전혀 없기 때문이다. 잠시 후 Nelle은 스크립트가 아무것도 출력하지 않아서 실행 여부나 진행 속도를 알 수 없다는 것을 깨닫는다. Ctrl-C로 작업을 중단하고, 위쪽 화살표로 명령을 다시 불러와서 다음과 같이 수정한다.
$ for datafile in NENE*[AB].txt; do echo $datafile; bash goostats $datafile stats-$datafile; done
쉘에서 Ctrl-A
를 눌러 현재 줄의 시작으로, Ctrl-E
를 눌러 줄의 끝으로 빠르게 이동할 수 있다.
이번에는 프로그램이 실행되면서 약 5초마다 한 줄씩 출력한다.
NENE01729A.txt
NENE01729B.txt
NENE01736A.txt
...
1518개 파일 × 5초 ÷ 60을 계산하면, 전체 스크립트 실행에 약 2시간 정도 소요될 것으로 예상된다.
이것이 바로 for 루프의 진정한 가치다. 수작업으로는 불가능한 2시간짜리 반복 작업을, Nelle은 단 몇 줄의 코드로 자동화했다. 마지막으로 확인차 다른 터미널 창을 열어서 north-pacific-gyre/2012-07-03
디렉토리로 이동하고, cat stats-NENE01729B.txt
로 출력 파일 중 하나를 검토해본다. 결과가 만족스럽다.
이제 Nelle은 컴퓨터가 알아서 1,518개 파일을 처리하는 동안, 커피를 마시며 밀린 논문을 읽을 시간이다. 백업 파일 생성에서 시작된 여정이 드디어 진짜 연구 성과로 이어지는 순간이다.
5.5 AI 증강 루프
Nelle처럼 복잡한 루프를 작성할 때, AI 도구의 도움을 받을 수 있다. 전통적인 쉘 프로그래밍과 AI 보조 개발을 적절히 조합하면 더욱 효율적이고 안전한 작업이 가능하다.
Claude Code는 루프 작성 과정에서 다양한 도움을 제공한다. 구조 검증 측면에서 “이 for 루프에 문제가 있는지 확인해주세요”라고 요청하면, 잠재적 오류를 찾아내고 개선안을 제시한다. Dry Run 코드가 필요할 때는 “안전한 시험 실행 코드를 작성해주세요”라고 하면, echo 명령을 활용한 검증 코드를 자동 생성한다. 성능 최적화가 필요한 경우 “이 루프를 더 효율적으로 개선할 방법이 있나요”라는 질문으로 병렬 처리나 조건 최적화 방안을 얻을 수 있다.
# Claude Code 검증 후 개선된 안전한 루프
for file in NENE*[AB].txt; do
echo "Processing $file..."
if bash goostats "$file" "stats-$file"; then
echo "✓ Successfully processed $file"
else
echo "✗ Error processing $file" >&2
continue
fi
done
GitHub Copilot CLI는 자연어로 복잡한 루프 로직을 생성할 때 유용하다. 병렬 처리나 특정 조건의 루프가 필요한 경우, 직관적인 한국어 설명만으로도 적절한 코드를 생성할 수 있다. 또한 기존 루프 코드의 동작 방식을 이해하고 싶을 때 설명 요청 기능을 활용할 수 있다.
# 병렬 처리 루프 생성
gh copilot suggest "1518개 데이터 파일을 병렬로 처리하되 동시에 최대 4개만"
# 기존 루프 설명 요청
gh copilot explain "for file in *.txt; do goostats $file > stats-$file; done"
AI 도구를 안전하게 사용하기 위해서는 몇 가지 핵심 원칙을 지켜야 한다. 가장 중요한 것은 Dry Run 우선 원칙이다. AI가 생성한 코드라도 반드시 echo 명령으로 먼저 시뮬레이션해야 한다. 백업 역시 필수적이다. 중요한 파일을 처리하기 전에는 cp 명령으로 원본을 보존해야 한다. 전체 작업을 한 번에 실행하지 말고 작은 파일셋으로 먼저 테스트하는 단계적 실행도 중요하다. 마지막으로 AI 도구가 완벽하지 않으므로 출력 파일의 내용을 반드시 검증해야 한다.
for 루프의 현대적 가치는 명확하다. 수작업으로는 불가능한 대량 반복 작업을 몇 줄의 코드로 자동화할 수 있다. Dry Run으로 실행 전 검증하여 안전성을 확보하고, 몇 개 파일부터 수천 개 파일까지 동일한 방법으로 확장할 수 있다. 특히 AI 도구와 조합하면 더욱 효율적인 개발이 가능하다.