Bash에서 파일 내용을 반복


1387

Bash 를 사용하여 텍스트 파일의 각 줄을 어떻게 반복 합니까?

이 스크립트로 :

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

화면 에이 출력이 나타납니다.

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(나중에 $p화면에 출력하는 것보다 더 복잡한 것을하고 싶습니다 .)


환경 변수 SHELL 은 (env에서) 다음과 같습니다.

SHELL=/bin/bash

/bin/bash --version 산출:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

cat /proc/version 산출:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

peptides.txt 파일에는 다음이 포함됩니다.

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

19
오, 나는 여기에 많은 일들이 일어났다는 것을 본다 : 모든 의견이 삭제되었고 질문이 다시 열렸다. 참고로, 값을 변수에 할당하여 파일을 한 줄씩 읽기 의 허용 된 대답은 표준적인 방식으로 문제를 해결하며 여기서 허용 된 것보다 우선해야합니다.
— fedorqui 'SO 중지 피해'

답변:


2090

이를 수행하는 한 가지 방법은 다음과 같습니다.

while read p; do
  echo "$p"
done <peptides.txt

주석에서 지적했듯이 이것은 선행 공백을 자르고 백 슬래시 시퀀스를 해석하며 마지막 줄에 줄 바꿈이 누락 된 경우 마지막 줄을 건너 뛰는 부작용이 있습니다. 이것이 우려되는 경우 다음을 수행 할 수 있습니다.

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

예외적으로 루프 본문이 표준 입력에서 읽을 수있는 경우 다른 파일 설명자를 사용하여 파일을 열 수 있습니다.

while read -u 10 p; do
  ...
done 10<peptides.txt

여기서 10은 임의의 숫자 (0, 1, 2와는 다름)입니다.


7
마지막 줄을 어떻게 해석해야합니까? 파일 peptides.txt가 표준 입력으로 리디렉션되고 while 블록 전체로 리디렉션됩니까?
— Peter Mortensen

11
"Slurp peptides.txt를이 while 루프에 넣으므로 '읽기'명령에 사용할 것이 있습니다." 내 "고양이"방법도 비슷합니다. 명령의 출력을 '읽기'에 의해 소비하기 위해 while 블록으로 보내면 다른 프로그램 만 실행하여 작업을 완료합니다.
— Warren Young

8
이 방법은 파일의 마지막 줄을 건너 뛰는 것 같습니다.
— xastor

5
큰 따옴표 라인! 에코 "$ p"와 파일 .. 당신이하지 않으면 물릴 것입니다 날 믿어! 알아! lol
— Mike Q

5
줄 바꿈으로 끝나지 않으면 두 버전 모두 마지막 줄을 읽지 못합니다. 항상 사용while read p || [[ -n $p ]]; do ...
— dawg

447
cat peptides.txt | while read line 
do
   # do something with $line here
done

그리고 한 줄짜리 변형 :

cat peptides.txt | while read line; do something_with_$line_here; done

후행 줄 바꿈이없는 경우이 옵션은 파일의 마지막 줄을 건너 뜁니다.

다음과 같은 방법으로이를 피할 수 있습니다.

cat peptides.txt | while read line || [[ -n $line ]];
do
   # do something with $line here
done

68
일반적으로 하나의 인수만으로 "cat"을 사용하는 경우 잘못된 것이 있습니다 (또는 차선책).
— JesperE

27
예, 브루노보다 효율적이지 않습니다. 불필요하게 다른 프로그램을 시작하기 때문입니다. 효율성이 중요하다면 브루노의 방식으로하십시오. "redirect in from"구문이 작동하지 않는 다른 명령과 함께 사용할 수 있기 때문에 제 방식을 기억합니다.
— Warren Young

74
이것에 대한 또 다른 심각한 문제가 있습니다. while 루프는 파이프 라인의 일부이기 때문에 서브 쉘에서 실행되므로 루프 내부에 설정된 모든 변수는 종료 될 때 손실됩니다 ( bash-hackers.org/wiki/doku 참조) . php / mirroring / bashfaq / 024 ). 이것은 매우 성 가실 수 있습니다 (루프에서하려는 일에 따라 다름).
— Gordon Davisson

25
필자는 종종 "head file |"으로 프로토 타입을 작성하기 때문에 많은 명령의 시작으로 "cat file |"을 사용합니다.
— mat kelcey

62
이것은 효율적이지 않을 수도 있지만 다른 답변보다 훨씬 읽기 쉽습니다.
— 야만인 리더

144

옵션 1a : While 루프 : 한 번에 한 줄 : 입력 리디렉션

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

옵션 1b : While 루프 : 한 번에 한 줄 :
파일을 열고 파일 설명자 (이 경우 파일 설명자 # 4)에서 읽습니다.

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

옵션 1b의 경우 : 파일 디스크립터를 다시 닫아야합니까? 예를 들어 루프는 내부 ​​루프 일 수 있습니다.
— Peter Mortensen

3
프로세스가 종료되면 파일 디스크립터가 정리됩니다. fd 번호를 재사용하기 위해 명시 적으로 닫기를 수행 할 수 있습니다. fd를 닫으려면 다음과 같이 &-구문을 사용하여 다른 exec를 사용하십시오. exec 4 <&
— Stan Graves

1
옵션 2에 감사드립니다. 루프 내의 stdin에서 읽어야했기 때문에 옵션 1에 큰 문제가 발생했습니다. 이 경우 옵션 1이 작동하지 않습니다.
— masgo

4
옵션 2는 사용 하지 않는 것이 좋습니다 . @masgo 옵션 (1B)는이 경우에 작동해야하고, 대체하여 옵션 (1A)의 입력 재 지정 구문과 결합 될 수 done < $filename와 done 4<$filename어떤 경우에는 그냥 대체 할 수있는 당신이 명령 매개 변수에서 파일 이름을 읽고 싶은 경우에 유용합니다 ( $filename로 $1).
— Egor Hans

루프 tail -n +2 myfile.txt | grep 'somepattern' | cut -f3내에서 ssh 명령을 실행하는 동안 (stdin 사용) 과 같은 파일 내용을 반복해야합니다 . 옵션 2가 유일한 방법 인 것 같습니다.
— user5359531

85

이것은 다른 답변보다 낫지 않지만 공백없이 파일에서 작업을 수행하는 또 다른 방법입니다 (주석 참조). 별도의 스크립트 파일을 사용하는 추가 단계없이 텍스트 파일의 목록을 파헤 치려면 종종 한 명의 라이너가 필요하다는 것을 알았습니다.

for word in $(cat peptides.txt); do echo $word; done

이 형식을 사용하면 하나의 명령 줄에 모두 넣을 수 있습니다. "echo $ word"부분을 원하는대로 변경하면 세미콜론으로 구분 된 여러 명령을 실행할 수 있습니다. 다음 예제는 파일 내용을 사용자가 작성한 두 개의 다른 스크립트에 대한 인수로 사용합니다.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done

또는 이것을 스트림 편집기처럼 사용하려는 경우 (sed 학습) 다음과 같이 출력을 다른 파일로 덤프 할 수 있습니다.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt

텍스트 파일을 한 줄에 한 단어로 만든 텍스트 파일을 사용했기 때문에 위와 같이 사용했습니다. (주석 참조) 단어 / 줄을 나누고 싶지 않은 공백이 있으면 조금 더 나빠지지만 동일한 명령이 여전히 다음과 같이 작동합니다.

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

이것은 단지 쉘이 공백이 아니라 개행으로 만 분할하도록 지시 한 다음 환경을 이전 상태로 되돌립니다. 이 시점에서 모든 것을 한 줄로 짜는 대신 쉘 스크립트에 넣는 것을 고려할 수 있습니다.

행운을 빌어 요!


6
bash $ (<peptides.txt)는 아마도 더 우아 할 수도 있지만 Joao가 말한 것처럼 여전히 잘못되었습니다. 공간이나 줄 바꿈이 같은 곳에서 명령 대체 논리를 수행하고 있습니다. 줄에 공백이 있으면 루프는 해당 한 줄에 대해 TWICE 이상을 실행합니다. 따라서 코드는 올바르게 읽어야합니다. $ (<peptides.txt); .... 공백이 없다는 사실을 알고 있다면 줄은 단어와 같으며 괜찮습니다.
— maxpolk

2
@ JoaoCosta, maxpolk : 내가 고려하지 않은 좋은 점. 원래 게시물을 수정하여 수정했습니다. 감사!
— 강력한

2
를 사용 for하면 입력 토큰 / 라인이 쉘 확장에 종속되므로 일반적으로 바람직하지 않습니다. 이것을보십시오 : for l in $(echo '* b c'); do echo "[$l]"; done-당신이 볼 수 있듯이, *-원래 인용 된 리터럴 이지만 -현재 디렉토리의 파일로 확장됩니다.
— mklement0

2
@dblanchard : $ IFS를 사용하는 마지막 예는 공백을 무시해야합니다. 그 버전을 사용해 보셨습니까?
— mightypile

4
중요한 문제가 해결 될 때이 명령이 훨씬 더 복잡 해지는 방법은 for파일 라인을 반복 하는 데 사용 하는 것이 좋지 않은 이유를 잘 보여줍니다 . 또한 @ mklement0에 언급 된 확장 측면 (아마도 이스케이프 된 따옴표를 가져 와서 우회 할 수는 있지만 상황이 더 복잡하고 읽기 어려워집니다).
— Egor Hans

69

다른 답변으로 다루지 않는 몇 가지 더 :

구분 된 파일에서 읽기

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt

프로세스 대체를 사용하여 다른 명령의 출력에서 ​​읽기

while read -r line; do
  # process the line
done < <(command ...)

이 방법은 command ... | while read -r line; do ...여기의 while 루프는 후자의 경우와 같이 서브 쉘이 아닌 현재 쉘에서 실행되기 때문에이 . 관련 게시물 참조 while 루프 내에서 수정 된 변수는 기억되지 않습니다 .

예를 들어 널로 구분 된 입력에서 읽기 find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

관련 읽기 : BashFAQ / 020-줄 바꿈, 공백 또는 둘 다를 포함하는 파일 이름을 어떻게 찾고 안전하게 처리 할 수 ​​있습니까?

한 번에 여러 파일에서 읽기

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt

@chepner의 대답을 바탕으로 여기에 :

-ubash 확장입니다. POSIX 호환성을 위해 각 호출은 다음과 같습니다.read -r X <&3 .

전체 파일을 배열로 읽기 (4 이전의 Bash 버전)

while read -r line; do
    my_array+=("$line")
done < my_file

파일이 불완전한 줄 (끝에 줄 바꿈)로 끝나는 경우 :

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

전체 파일을 배열로 읽기 (배시 버전 4x 이상)

readarray -t my_array < my_file

또는

mapfile -t my_array < my_file

그리고

for line in "${my_array[@]}"; do
  # process the lines
done

관련 게시물:


대신에 command < input_filename.txt항상input_generating_command | commandcommand < <(input_generating_command)
— masterxilo

1
파일을 배열로 읽어 주셔서 감사합니다. 정확히 필요한 것은, 각 줄을 두 번 구문 분석하고 새 변수를 추가하고 유효성 검사 등을 수행해야하기 때문입니다.
— frank_108

45

다음과 같이 while 루프를 사용하십시오.

while IFS= read -r line; do
   echo "$line"
done <file

노트:

  1. IFS제대로 설정하지 않으면 들여 쓰기가 손실됩니다.

  2. 거의 항상 -r 옵션을 read와 함께 사용해야합니다.

  3. 줄을 읽지 마십시오 for


2
왜 -r옵션입니까?
— David C. Rankin

2
@ DavidC.Rankin -r 옵션은 백 슬래시 해석을 방지합니다. Note #2자세한 설명이있는 링크입니다 ...
— Jahid

이것을 다른 답변에서 "read -u"옵션과 결합하면 완벽합니다.
— Florin Andrei

@FlorinAndrei : 위의 예제는 -u옵션이 필요하지 않습니다. 다른 예제에 대해 이야기하고 -u있습니까?
— Jahid

귀하의 링크를 살펴본 결과, 참고 2의 링크를 링크하는 답변이 없다는 것에 놀랐습니다. 해당 페이지에는 해당 주제에 대해 알아야 할 모든 것이 있습니다. 아니면 링크 전용 답변이 권장되지 않습니까?
— Egor Hans

14

이 파일이 있다고 가정하십시오.

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR

많은 Bash 솔루션에서 읽은 파일 출력의 의미를 변경하는 네 가지 요소가 있습니다.

  1. 빈 줄 4;
  2. 두 줄의 선행 또는 후행 공백;
  3. 개별 라인의 의미를 유지합니다 (즉, 각 라인은 레코드입니다).
  4. 6 번 줄은 CR로 끝나지 않았습니다.

빈 줄을 포함하고 CR없이 끝나는 줄을 포함하여 텍스트 파일을 한 줄씩 표시하려면 while 루프를 사용해야하며 마지막 줄에 대한 대체 테스트가 있어야합니다.

다음은 파일을 변경할 수있는 메소드입니다 ( cat반환 되는 항목과 비교 ).

1) 마지막 줄과 앞뒤 공백을 잃습니다.

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'

( while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt대신 대신 선행 및 후행 공백을 유지하지만 CR로 끝나지 않으면 마지막 줄을 잃게됩니다)

2) with 프로세스 대체를 사용 cat하면 전체 파일을 한 번에 읽고 개별 줄의 의미를 잃습니다.

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'

(당신이 제거하면 "에서 $(cat /tmp/test.txt)당신을 대신 한 꿀꺽보다 말씀으로 파일 단어를 읽어 보시기 바랍니다. 또한 의도 아닐 것 ...)


파일을 한 줄씩 읽고 모든 간격을 유지하는 가장 강력하고 간단한 방법은 다음과 같습니다.

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'

선행 및 거래 공간을 제거하려면 IFS=부품을 제거하십시오 .

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'

종단없이 (A 텍스트 파일 \n, 매우 일반적인 반면, POSIX에서 깨진 것으로 간주됩니다. 당신이 후행 믿을 수있는 경우에 \n필요하지 않은 || [[ -n $line ]]에while 루프 .)

BASH FAQ 에서 더보기


13

줄 바꿈 문자로 읽기를 중단하지 않으려면-

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

그런 다음 파일 이름을 매개 변수로 사용하여 스크립트를 실행하십시오.


4
#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done

7
이 답변에는 mightypile의 답변에 언급 된주의 사항이 필요하며 따옴표없는 "$ x"로 인해 쉘 메타 문자가 포함 된 행이 있으면 실패 할 수 있습니다.
— Toby Speight

7
나는 사람들이 아직 평소에 읽지 말아야 할 줄 알지 못한 것에 놀랐습니다 .
— Egor Hans

3

다음은 다른 프로그램 출력의 라인을 루프하고 하위 문자열을 확인하고 변수에서 큰 따옴표를 삭제하고 루프 외부에서 해당 변수를 사용하는 방법에 대한 실제 예입니다. 많은 사람들이 조만간이 질문들을하고있는 것 같습니다.

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

루프 외부에서 변수를 선언하고 값을 설정 한 후 루프 외부에서 사용하려면 <<< "$ (...)" 구문이 필요합니다. 응용 프로그램은 현재 콘솔의 컨텍스트 내에서 실행해야합니다. 명령 주위의 따옴표는 줄 바꿈 출력 스트림을 유지합니다.

하위 문자열에 대한 루프 일치는 이름 = 값 쌍 을 읽고 마지막 = 문자 의 오른쪽 부분을 분할하고 첫 번째 따옴표를 삭제하고 마지막 따옴표를 삭제하며 다른 곳에서 사용할 깨끗한 값을 갖습니다.


3
대답은 정확하지만 어떻게 끝났는지 이해합니다. 필수 방법은 다른 많은 답변에서 제안한 것과 동일합니다. 또한 FPS 예제에서는 완전히 익사합니다.
— Egor Hans

0

이것은 다소 늦었지만, 누군가를 도울 수 있다는 생각으로 대답을 추가하고 있습니다. 또한 이것이 최선의 방법이 아닐 수도 있습니다. head명령은 -n인수 와 함께 사용되어 파일의 시작 부분 에서 n 줄 을 읽으며 마찬가지로 tail명령을 아래에서 읽을 수 있습니다. 이제 가져 n 번째 파일에서 라인을, 우리는 머리를 n 개의 행을 꼬리에 파이프로 연결된 데이터에서 단 1 개 라인, 파이프 데이터.

   TOTAL_LINES=`wc -l $USER_FILE | cut -d " " -f1 `
   echo $TOTAL_LINES       # To validate total lines in the file

   for (( i=1 ; i <= $TOTAL_LINES; i++ ))
   do
      LINE=`head -n$i $USER_FILE | tail -n1`
      echo $LINE
   done

1
이러지 마 줄 번호를 반복하고 sed또는 head+를 사용 하여 각 개별 줄을 가져 오는 tail것은 매우 비효율적이며 물론 다른 솔루션 중 하나를 사용하지 않는 이유에 대한 의문을 제기합니다. 줄 번호를 알아야하는 경우 while read -r루프에 카운터를 추가하거나 루프 nl -ba앞의 각 줄에 줄 번호 접두사를 추가하는 데 사용 하십시오.
— 트리플 리

-1

@ 피터 : 이것은 당신을 위해 해결할 수 있습니다-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

이것은 출력을 반환합니다

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL


3
이 답변은 위의 좋은 답변으로 설정된 모든 원칙을 물리 치고 있습니다!
— codeforester 2018 년

3
이 답변을 삭제하십시오.
— dawg

3
이제 여러분, 과장하지 마십시오. 대답은 나쁘지만 최소한 간단한 사용 사례에서는 효과가있는 것 같습니다. 그것이 제공되는 한, 잘못된 답변은 답변의 존재 권을 빼앗아 가지 않습니다.
— Egor Hans

3
@ EgorHans, 나는 매우 동의하지 않습니다 : 대답의 요점은 사람들에게 소프트웨어 작성 방법을 가르치는 것입니다. 사람들에게 당신이 아는 방식으로 일을하도록 가르치는 것은 그들에게 해롭고 그들의 소프트웨어를 사용하는 사람들 (버그 소개 / 예기치 않은 행동 등)은 다른 사람들에게 고의로 해를 끼칩니다. 유해한 것으로 알려진 답변은 잘 선별 된 교육 자료에 "존재할 권리"가 없습니다 (투표 및 신고를하는 사람들이 여기서하고있는 것으로 정확히 선별).
— Charles Duffy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.