bash의 변수에서 한 줄씩 읽을 수 있습니까?

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

예 :

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

Answer

프로세스 대체와 함께 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") 

참고 : shellcheck sc2031 에 따라 [미묘하게] 피하기 위해 파이프보다 프로세스 대체를 사용하는 것이 좋습니다. 하위 셸을 만듭니다.

또한 변수 이름을 jobs로 지정하면 공통 셸 명령의 이름이기도하므로 혼동을 일으킬 수 있습니다.

p>

댓글

  • 모든 공백을 유지하려면 while IFS= read …를 사용하십시오. 해석을 방지하려면 read -r
  • ‘ fred.bear가 언급 한 포인트를 수정하고 echo

    , 스크립트가 길 들이지 않은 입력에서도 작동하도록합니다.

  • 여러 줄 변수에서 읽으려면 여기 문자열이 printf에서 파이핑하는 것보다 선호됩니다 (l0b0 ‘의 답변).
  • @ata ‘이 “를 들었지만 div> preferable ” 자주 사용합니다. herestring에는 항상 쓰기 가능한 /tmp 디렉토리가 필요합니다. 임시 작업 파일을 만들 수 있습니다. /tmp가 읽기 전용 (사용자가 변경할 수 없음)으로 제한된 시스템에있는 경우, 대체 솔루션을 사용할 가능성에 만족할 것입니다. 지. printf 파이프를 사용합니다.
  • 두 번째 예에서 여러 줄 변수에 ‘ 후행 개행 문자는 마지막 요소를 잃게됩니다. 다음으로 변경 : printf "%s\n" "$var" | while IFS= read -r line

답변

한 줄씩 명령 줄 출력 ( 설명 ) :

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

이미 변수에 데이터가 있음 :

printf %s "$foo" | … 

printf %s "$foo"echo "$foo", 그러나 $foo를 문자 그대로 인쇄하는 반면 echo "$foo"$foo를 옵션으로 해석 할 수 있습니다. -로 시작하고 일부 셸에서 $foo의 백 슬래시 시퀀스를 확장 할 수있는 경우 echo 명령에 추가합니다.

일부 셸 (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 } 

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

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

설명 : 를 단일 줄 바꿈으로 만들면 줄 바꿈에서만 단어 분할이 발생합니다 (기본 설정의 공백 문자와 반대). set -f는 globbing (예 : 와일드 카드 확장)을 끕니다. 그렇지 않으면 명령 대체 $(jobs) 또는 변수 대체

. for 루프는 명령 출력에서 비어 있지 않은 모든 줄인 $(jobs)의 모든 부분에서 작동합니다. 마지막으로 globbing 및 IFS 설정을 기본값과 동일한 값으로 복원합니다.

댓글

  • IFS 설정 및 IFS 설정 해제에 문제가 있습니다. 올바른 방법은 IFS의 이전 값을 저장하고 IFS를 이전 값으로 다시 설정하는 것입니다. 저는 ‘ 배쉬 전문가는 아니지만 제 경험상 원래 bahavior로 돌아갑니다.
  • @BjornRoche : 함수 내에서

    . 전역 범위 값에 영향을주지 않습니다. ‘ IIRC, unset IFS는 ‘ 기본값으로 돌아 가지 않습니다 (확실히 ‘ t는 ‘ 사전에 기본값이 아니면 작동합니다.)

  • set를 사용하는지 궁금합니다. 마지막 예에 표시된 것이 정확합니다.코드 스 니펫은 set +f가 처음에 활성화되었다고 가정하므로 마지막에 해당 설정을 복원합니다. 그러나이 가정은 잘못되었을 수 있습니다. set -f가 처음에 활성화 된 경우 어떻게됩니까?
  • @Binarus 기본값과 동일한 설정 만 복원합니다. 실제로 원래 설정을 복원하려면 더 많은 작업을 수행해야합니다. set -f의 경우 원본 $-를 저장합니다. IFS의 경우 ‘ ‘에

    그리고 설정되지 않은 케이스를 지원하려고합니다. 복원하려면 IFS가 설정된 상태로 유지되는 불변성을 적용하는 것이 좋습니다.

  • local를 사용하면 local -는 셸 옵션을 로컬로 만들고 local IFSIFS 지역. 불행히도 local는 함수 내에서만 유효하므로 코드 재구성이 필요합니다. IFS이 항상 설정되어 있다는 정책을 도입하라는 귀하의 제안도 매우 합리적으로 들리고 문제의 가장 큰 부분을 해결합니다. 감사합니다!

답변

문제 : 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= 

댓글

  • 정말 감사합니다 !! 위의 모든 솔루션이 저에게 실패했습니다.
  • bash에서 while read 루프로 파이핑하면 while 루프가 서브 쉘에 있으므로 변수가 없습니다 ‘ t 글로벌. while read;do ;done <<< "$var"는 루프 본문을 서브 쉘이 아닙니다. (최근 bash에는 ksh가 항상 가지고 있었던 것처럼 서브 쉘에 cmd | while 루프의 본문을 넣는 옵션이 있습니다.)
  • 이 관련 게시물 .
  • 비슷한 상황에서 IFS을 올바르게 처리하는 것이 놀랍도록 어려웠습니다. 이 솔루션에도 문제가 있습니다. IFS가 처음에 전혀 설정되지 않은 경우 (즉, 정의되지 않은 경우)? 해당 코드 스 니펫 다음에 모든 경우에 정의됩니다. ‘ 정확하지 않은 것 같습니다.

답변

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

참조 :

댓글

  • -r도 좋은 생각입니다. \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your IFS =`(공백 손실을 방지하는 데 필수적 임)를 방지합니다.
  • 이 솔루션 만 저에게 효과적이었습니다. 감사합니다.
  • ‘이 솔루션은 @dogbane의 댓글에 언급 된 것과 동일한 문제를 겪지 않습니다 ‘의 대답? 변수의 마지막 줄이 개행 문자로 끝나지 않으면 어떻게됩니까?
  • 이 답변은 변수의 내용을 while read에 공급하는 가장 깨끗한 방법을 제공합니다.

답변

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

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

면책 조항 : 끔찍한 예이지만 아마 올 수 있습니다. 자신보다 더 나은 명령을 사용하는 것이 좋습니다.

댓글

  • It ‘ 좋은 방법입니다. 하지만 내 시스템에 임시 파일이있는 / var / tmp를 흩뿌립니다. 어쨌든 +1
  • @eugene : ‘ 재미 있네요. 어떤 시스템 (distro / OS)이 켜져 있습니까?
  • It ‘ s FreeBSD 8. 재생산 방법 : put readarray를 사용하고 함수를 몇 번 호출합니다.
  • 좋습니다. @sehe. +1

답변

이 문제를 해결하는 일반적인 패턴은 다른 답변에 나와 있습니다. p>

그러나 그것이 얼마나 효율적인지 확신 할 수는 없지만 제 접근 방식을 추가하고 싶습니다. 그러나 (적어도 저에게는) 상당히 이해할 수 있으며 원래 변수 (read는 문제의 변수에 후행 개행 문자가 있어야하며 따라서 변수를 변경하는 변수를 추가하여 서브 쉘을 생성하지 않으며 (모든 파이프 기반 솔루션이 수행하는) 여기서 사용하지 않습니다. -strings (자체 문제가 있음), 프로세스 대체를 사용하지 않습니다 (반대하는 것은 아니지만 가끔 이해하기가 약간 어렵습니다).

사실, 왜 “의 통합 RE는 거의 사용되지 않습니다.아마도 그것들은 이식성이 없지만 OP는 bash 태그를 사용했기 때문에 저를 멈추지 않을 것입니다 🙂

#!/bin/bash function ProcessLine() { printf "%s" "$1" } function ProcessText1() { local Text=$1 local Pattern=$"^([^\n]*\n)(.*)$" while [[ "$Text" =~ $Pattern ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done ProcessLine "$Text" } function ProcessText2() { local Text=$1 local Pattern=$"^([^\n]*\n)(.*)$" while [[ "$Text" =~ $Pattern ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done } function ProcessText3() { local Text=$1 local Pattern=$"^([^\n]*\n?)(.*)$" while [[ ("$Text" != "") && ("$Text" =~ $Pattern) ]]; do ProcessLine "${BASH_REMATCH[1]}" Text="${BASH_REMATCH[2]}" done } MyVar1=$"a1\nb1\nc1\n" MyVar2=$"a2\n\nb2\nc2" MyVar3=$"a3\nb3\nc3" ProcessText1 "$MyVar1" ProcessText1 "$MyVar2" ProcessText1 "$MyVar3" 

출력 :

root@cerberus:~/scripts# ./test4 a1 b1 c1 a2 b2 c2a3 b3 c3root@cerberus:~/scripts# 

참고 :

동작은 사용하고 있습니다. 위의 예에서는 ProcessText1를 사용했습니다.

참고

  • ProcessText1는 줄 끝에서 줄 바꿈 문자를 유지합니다.
  • ProcessText1는 변수의 마지막 줄 (텍스트 포함)을 처리합니다. c3) 해당 줄에는 후행 개행 문자가 포함되어 있지 않습니다. 누락 된 후행 개행 문자로 인해 스크립트 실행 후 명령 프롬프트가 마지막에 추가됩니다. 출력에서 분리되지 않고 변수의 줄.
  • ProcessText1는 항상 변수의 마지막 줄 바꿈과 변수 끝 사이의 부분을 줄로 간주합니다. , 비어 있어도; 물론 그 줄은 비어 있든 없든 후행 개행 문자가 없습니다. 즉, 변수의 마지막 문자가 개행 문자 인 경우에도 ProcessText1는 마지막 개행과 변수 끝 사이의 빈 부분 (널 문자열)을 (아직 비어 있음) 라인을 처리하고 라인 처리에 전달합니다. ProcessLine에 대한 두 번째 호출을 적절한 비어있는 경우 확인 조건으로 래핑하여이 동작을 쉽게 방지 할 수 있습니다. 하지만 그대로 두는 것이 더 논리적이라고 생각합니다.

ProcessText1ProcessLine 두 곳에, 라인을 처리하는 함수를 호출하는 대신 라인을 직접 처리하는 코드 블록을 거기에 배치하려는 경우 불편할 수 있습니다. 오류가 발생하기 쉬운 코드를 반복해야합니다.

반대로 ProcessText3는 줄을 처리하거나 한 곳에서만 각 함수를 호출하여 코드에 의한 함수 호출은 쉬운 일이 아닙니다. 이는 하나가 아닌 두 개의 while 조건의 비용으로 발생합니다. 구현 차이를 제외하고 ProcessText3ProcessText1와 정확히 동일하게 작동하지만, 마지막 줄 바꿈 문자 사이의 부분을 고려하지 않습니다. 해당 부분이 비어있는 경우 변수와 변수의 끝을 줄로 표시합니다. 즉, ProcessText3는 해당 개행 문자가 변수의 마지막 문자 인 경우 변수의 마지막 개행 문자 이후에 행 처리로 들어 가지 않습니다.

ProcessText2ProcessText1처럼 작동하지만 줄에 줄 바꿈 문자가 있어야한다는 점이 다릅니다. 즉, 변수의 마지막 줄 바꿈 문자와 변수 끝 사이의 부분은 줄로 간주되지 않고 조용히 버려집니다. 결과적으로 변수에 개행 문자가 포함되어 있지 않으면 행 처리가 전혀 발생하지 않습니다.

위에 표시된 다른 솔루션보다 그 접근 방식이 더 마음에 들지만 아마도 뭔가 놓친 것 같습니다. bash 프로그래밍, 다른 셸에별로 관심이 없음).

답변

< < <를 사용하여 개행 문자가 포함 된 변수에서 간단히 읽을 수 있습니다. -분리 된 데이터 :

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

댓글

  • Unix에 오신 것을 환영합니다. & Linux! 이것은 본질적으로 4 년 전의 답변과 동일합니다. 기여할 새로운 것이 없으면 답변을 게시하지 마십시오.

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다