명령을 붙여 넣었는데 프롬프트가 > 로 바뀌고 계속 입력을 기다리는 것은, 셸이 아직 명령이 끝나지 않았다고 보고 있다는 뜻이다. 이 > 는 bash 의 보조 프롬프트이며 변수 PS2 로 정해진다. 실행한 명령과는 아무 관계가 없다.
원인은 정해져 있다.
따옴표가 닫히지 않았다.
do 는 썼는데 done 이 없다. if 는 썼는데 fi 가 없다.
줄 끝의 역슬래시로 다음 줄을 기다리고 있다.
괄호나 중괄호가 닫히지 않았다.
빠져나오려면 Ctrl+C 를 누른다. 이미 입력한 내용은 실행되지 않는다.
done 은 별도의 명령이므로 줄바꿈이나 세미콜론으로 앞과 분리돼야 한다. 한 줄로 쓸 때는 세미콜론을 빠뜨리지 않는다.
while read -r repo; do echo "$repo"; done < repos.txt
REGION="ap-northeast-2"
while IFS= read -r repo; do
[ -z "$repo" ] && continue
if aws ecr describe-repositories --repository-names "$repo" --region "$REGION" >/dev/null 2>&1; then
echo "EXISTS: $repo"
else
echo "CREATE: $repo"
aws ecr create-repository --repository-name "$repo" --region "$REGION" >/dev/null
fi
done < repos.txt
IFS= read -r 이 관용구다. IFS= 는 앞뒤 공백이 잘리는 것을 막고, -r 은 역슬래시를 문자 그대로 읽게 한다.
두 가지를 조심한다. 마지막 줄에 개행이 없으면 그 줄을 읽지 못한다. 루프 뒤에 남은 값을 처리하거나 파일에 개행을 넣는다.
while IFS= read -r repo || [ -n "$repo" ]; do
...
done < repos.txt
루프 안에서 표준 입력을 읽는 명령이 있으면 목록을 먹어 버린다. ssh · aws 일부 명령이 그렇다. 해당 명령에 < /dev/null 을 붙이거나 다른 파일 디스크립터를 쓴다.
while IFS= read -r repo <&3; do
ssh "$repo" uptime
done 3< hosts.txt
파이프로 넘기는 형태(cat file | while read ...)는 서브셸에서 돌아 루프 안에서 바꾼 변수가 밖으로 나오지 않는다. 리다이렉션을 쓴다.
종료 코드가 기준이다. 출력 문자열로 판정하지 않는다.
if some_command; then
echo "성공"
else
echo "실패: $?"
fi
|| 로 이어 쓰는 형태도 같다.
aws ecr describe-repositories --repository-names "$repo" >/dev/null 2>&1 \
|| aws ecr create-repository --repository-name "$repo" >/dev/null
읽기 쉬운 쪽은 if 다. || 체인은 명령이 셋 이상이 되면 어느 것이 어느 실패에 붙는지 알기 어렵다.
set -e 를 켜 두었더라도 if 조건이나 || 왼쪽의 실패는 스크립트를 멈추지 않는다. 의도적으로 실패를 허용하는 자리에는 이 형태를 쓰고, 멈춰야 하는 곳에서는 조건 없이 그냥 호출한다.
HTTP 상태와 본문을 함께 본다. curl 은 서버가 500 을 돌려줘도 통신에 성공하면 종료 코드 0 이다.
#!/usr/bin/env bash
set -uo pipefail
check() {
local url=$1 body code
body=$(curl -sS -w '\n%{http_code}' -X POST "$url") || return 1
code=${body##*$'\n'}
body=${body%$'\n'*}
if [ "$code" != "200" ]; then
echo "FAIL ${url}: HTTP ${code}" >&2
return 1
fi
if ! echo "$body" | jq -e 'if type=="array" then all(.success=="true") else .success=="true" end' >/dev/null; then
echo "FAIL ${url}: success 가 true 가 아니다" >&2
return 1
fi
return 0
}
rc=0
for url in "http://api.example.com/start" "http://api.example.com/start_aa"; do
check "$url" || rc=1
done
[ "$rc" -eq 0 ] && echo OK
exit "$rc"
jq -e 는 결과가 false 나 null 이면 종료 코드 1 을 돌려준다. 문자열 비교를 셸에서 하지 않고 jq 에 맡길 수 있다.
응답이 JSON 객체 여러 개를 줄바꿈으로 이어 붙인 형태(NDJSON)라면 jq -s 로 배열로 묶어 처리한다.
curl -sS "$url" | jq -s -e 'all(.[]; .success=="true")' >/dev/null
curl: (3) port number ended with ' '
URL 안에 공백이나 따옴표가 섞여 들어간 것이다. 변수 치환 결과에 개행이나 공백이 붙었거나, 셸에서 따옴표를 잘못 닫은 경우다. URL 은 항상 따옴표로 감싸고, 변수에 담긴 값은 먼저 확인한다.
printf '[%s]\n' "$url"
url=${url//[$'\r\n\t ']/}
Windows 에서 만든 목록 파일을 리눅스에서 읽으면 줄 끝에 캐리지 리턴이 남아 이 증상이 자주 난다.
sed -i 's/\r$//' urls.txt