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 < $filenamedone 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.