명령의 여러 줄 출력을 포함하는 변수가 있습니다. 변수에서 한 줄씩 출력을 읽는 가장 효율적인 방법은 무엇입니까?
예 :
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>
댓글
답변
한 줄씩 명령 줄 출력 ( 설명 ) :
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 IFS
는IFS
지역. 불행히도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
에 대한 두 번째 호출을 적절한 비어있는 경우 확인 조건으로 래핑하여이 동작을 쉽게 방지 할 수 있습니다. 하지만 그대로 두는 것이 더 논리적이라고 생각합니다.
ProcessText1
는 ProcessLine
두 곳에, 라인을 처리하는 함수를 호출하는 대신 라인을 직접 처리하는 코드 블록을 거기에 배치하려는 경우 불편할 수 있습니다. 오류가 발생하기 쉬운 코드를 반복해야합니다.
반대로 ProcessText3
는 줄을 처리하거나 한 곳에서만 각 함수를 호출하여 코드에 의한 함수 호출은 쉬운 일이 아닙니다. 이는 하나가 아닌 두 개의 while
조건의 비용으로 발생합니다. 구현 차이를 제외하고 ProcessText3
는 ProcessText1
와 정확히 동일하게 작동하지만, 마지막 줄 바꿈 문자 사이의 부분을 고려하지 않습니다. 해당 부분이 비어있는 경우 변수와 변수의 끝을 줄로 표시합니다. 즉, ProcessText3
는 해당 개행 문자가 변수의 마지막 문자 인 경우 변수의 마지막 개행 문자 이후에 행 처리로 들어 가지 않습니다.
ProcessText2
는 ProcessText1
처럼 작동하지만 줄에 줄 바꿈 문자가 있어야한다는 점이 다릅니다. 즉, 변수의 마지막 줄 바꿈 문자와 변수 끝 사이의 부분은 줄로 간주되지 않고 조용히 버려집니다. 결과적으로 변수에 개행 문자가 포함되어 있지 않으면 행 처리가 전혀 발생하지 않습니다.
위에 표시된 다른 솔루션보다 그 접근 방식이 더 마음에 들지만 아마도 뭔가 놓친 것 같습니다. bash
프로그래밍, 다른 셸에별로 관심이 없음).
답변
< < <를 사용하여 개행 문자가 포함 된 변수에서 간단히 읽을 수 있습니다. -분리 된 데이터 :
while read -r line do echo "A line of input: $line" done <<<"$lines"
댓글
- Unix에 오신 것을 환영합니다. & Linux! 이것은 본질적으로 4 년 전의 답변과 동일합니다. 기여할 새로운 것이 없으면 답변을 게시하지 마십시오.
while IFS= read
…를 사용하십시오. 해석을 방지하려면read -r
echo
를, 스크립트가 길 들이지 않은 입력에서도 작동하도록합니다.
/tmp
디렉토리가 필요합니다. 임시 작업 파일을 만들 수 있습니다./tmp
가 읽기 전용 (사용자가 변경할 수 없음)으로 제한된 시스템에있는 경우, 대체 솔루션을 사용할 가능성에 만족할 것입니다. 지.printf
파이프를 사용합니다.printf "%s\n" "$var" | while IFS= read -r line