bash의 변수에서 한 줄씩 읽는 방법은 무엇입니까?


48

명령의 여러 줄 출력을 포함하는 변수가 있습니다. 변수에서 한 줄씩 출력을 읽는 가장 효율적인 방법은 무엇입니까?

예를 들면 다음과 같습니다.

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi

답변:


64

프로세스 대체와 함께 while 루프를 사용할 수 있습니다.

while read -r line
do
    echo "$line"
done < <(jobs)

여러 줄 변수를 읽는 가장 좋은 방법은 빈 IFS변수와 printf변수를 후행 줄 바꿈으로 설정하는 것입니다.

# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
   echo "$line"
done < <(printf '%s\n' "$var")

참고 : 쉘 체크 sc2031에 따르면 , 서브 쉘을 생성하는 것을 피하기 위해 파이프보다 프로세스 서브 스트 레이션을 사용하는 것이 바람직하다.

또한 변수 jobs의 이름을 지정하면 일반적인 쉘 명령의 이름이기 때문에 혼동을 일으킬 수 있습니다.


3
공백을 모두 유지하려면 while IFS= read... 을 사용하십시오 . 해석을 막으려면read -r
Peter.O

나는 변화뿐만 아니라, 언급 fred.bear 점 고정했습니다 echoprintf %s스크립트가 아닌 순종 입력으로도 작동 할 수 있도록를.
Gilles 'SO- 악마 중지'

여러 줄 변수를 읽으려면 printf에서 파이프하는 것보다 herestring을 사용하는 것이 좋습니다 (l0b0의 답변 참조).
ata

1
@ata이 "바람직한"것을 자주 들었지만, herestring /tmp은 임시 작업 파일을 만들 수 있기 때문에 항상 디렉토리를 쓸 수 있어야한다는 점에 유의해야 합니다. /tmp읽기 전용 (및 사용자가 변경할 수 없음) 으로 제한된 시스템 을 사용하는 경우 printf파이프 와 같은 대체 솔루션을 사용할 가능성에 만족할 것 입니다.
syntaxerror

1
두 번째 예에서 여러 줄 변수에 후행 줄 바꿈이 없으면 마지막 요소가 느슨해집니다. 그것을 변경 :printf "%s\n" "$var" | while IFS= read -r line
데이비드 H. 베넷에게

26

한 줄씩 명령 출력을 처리하려면 ( description ) :

jobs |
while IFS= read -r line; do
  process "$line"
done

변수에 이미 데이터가있는 경우 :

printf %s "$foo" | 

printf %s "$foo"는 거의 동일 echo "$foo"하지만 $foo문자 그대로 인쇄 하는 반면 echo 명령이로 시작하면 echo 명령에 대한 옵션으로 echo "$foo"해석 될 수 있으며 일부 쉘에서는 백 슬래시 시퀀스가 ​​확장 될 수 있습니다 .$foo-$foo

일부 셸 (ash, bash, pdksh, ksh 또는 zsh는 아님)에서 파이프 라인의 오른쪽은 별도의 프로세스에서 실행되므로 루프에서 설정 한 모든 변수가 손실됩니다. 예를 들어, 다음 줄 계산 스크립트는 이러한 셸에서 0을 인쇄합니다.

n=0
printf %s "$foo" |
while IFS= read -r line; do
  n=$(($n + 1))
done
echo $n

해결 방법은 나머지 스크립트 (또는 최소한 $n루프에서 값이 필요한 부분 )를 명령 목록 에 넣는 것입니다 .

n=0
printf %s "$foo" | {
  while IFS= read -r line; do
    n=$(($n + 1))
  done
  echo $n
}

비어 있지 않은 행에 대한 작업이 충분하고 입력이 크지 않은 경우 단어 분할을 사용할 수 있습니다.

IFS='
'
set -f
for line in $(jobs); do
  # process line
done
set +f
unset IFS

설명 : IFS단일 줄 바꿈으로 설정하면 기본 설정의 공백 문자와 달리 줄 바꿈에서만 단어 분리가 발생합니다. set -f그렇지 않으면 명령 대체 $(jobs)또는 변수 substitutino 결과로 발생하는 글 로빙 (예 : 와일드 카드 확장)을 끕니다 $foo. for루프의 모든 부분에 작용하는 $(jobs)명령 출력 모든 비어 있지 않은 선이다. 마지막으로 글 로빙 및 IFS설정을 복원하십시오 .


IFS 설정 및 IFS 설정 해제에 문제가 있습니다. 올바른 방법은 IFS의 이전 값을 저장하고 IFS를 다시 이전 값으로 설정하는 것입니다. 나는 bash 전문가는 아니지만 내 경험에 따르면 원래 bahavior로 돌아갑니다.
Bjorn Roche

1
@BjornRoche : 함수 안에서을 사용하십시오 local IFS=something. 전역 범위 값에는 영향을 미치지 않습니다. IIRC unset IFS는 기본값으로 돌아 가지 않습니다 (그리고 사전에 기본값이 아닌 경우 확실히 작동하지 않습니다).
Peter Cordes

14

문제점 : while 루프를 사용하면 서브 쉘에서 실행되고 모든 변수가 손실됩니다. 솔루션 : for 루프 사용

# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'

for line in $variableWithSeveralLines; do
 echo "$line"

 # return IFS back if you need to split new line by spaces:
 IFS=$IFS_BAK
 IFS_BAK=
 lineConvertedToArraySplittedBySpaces=( $line )
 echo "{lineConvertedToArraySplittedBySpaces[0]}"
 # return IFS back to newline for "for" loop
 IFS_BAK=$IFS
 IFS=$'\n'

done 

# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=

정말 고맙습니다!! 위의 모든 솔루션이 실패했습니다.
hax0r_n_code

while readbash 에서 루프 로 파이프 하는 것은 while 루프가 서브 쉘에 있음을 의미하므로 변수가 전역 적이 지 않습니다. while read;do ;done <<< "$var"루프 바디를 서브 쉘이 아닌 것으로 만듭니다. (최근의 bash는 cmd | whileksh처럼 항상 루프 의 몸체를 서브 쉘 에 넣지 않는 옵션을 가지고 있습니다.)
Peter Cordes

이 관련 게시물 도 참조하십시오 .
와일드 카드

10

최근 bash 버전에서 mapfile또는 readarray을 사용 하여 명령 출력을 배열로 효율적으로 읽습니다.

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305

면책 조항 : 끔찍한 예이지만 ls보다 더 나은 명령을 사용할 수 있습니다.


좋은 방법이지만 내 시스템의 임시 파일로 / var / tmp를 씁니다. 어쨌든 +1
유진 야마 쉬

@eugene : 재미 있네요. 어떤 시스템 (distro / OS)이 있습니까?
sehe

FreeBSD 8입니다. 재현 방법 : readarray함수를 넣고 함수를 몇 번 호출합니다.
Eugene Yarmash

좋은 사람, @sehe. +1
Teresa e Junior

7
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

참고 문헌 :


3
-r좋은 생각이기도합니다. 그것은 \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your 공백을 잃는 것을 막기 위해 필수적인 IFS =`를 막습니다
Peter.O

-1

<<<를 사용하면 개행으로 구분 된 데이터가 포함 된 변수에서 간단히 읽을 수 있습니다.

while read -r line
do 
  echo "A line of input: $line"
done <<<"$lines"

1
유닉스 및 리눅스에 오신 것을 환영합니다! 이것은 본질적으로 4 년 전의 답변을 복제합니다. 기고 할 새로운 것이 없다면 답을 올리지 마십시오.
G-Man
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.