Bash에서 파일 또는 STDIN을 읽는 방법은 무엇입니까?


244

다음 Perl 스크립트 ( my.pl)는 명령 행 args의 파일 또는 STDIN에서 읽을 수 있습니다.

while (<>) {
   print($_);
}

perl my.plSTDIN에서 perl my.pl a.txt읽은 다음에서 읽습니다 a.txt. 이것은 매우 편리합니다.

Bash에 해당하는 것이 있습니까?

답변:


409

다음 솔루션은 $1표준 입력에서 첫 번째 매개 변수로 파일 이름으로 스크립트를 호출 한 경우 파일에서 읽습니다 .

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

대체이 ${1:-...}소요 $1그렇지 않으면 자신의 프로세스의 표준 입력의 파일 이름을 사용하는 정의 된 경우.


1
좋았어. 또 다른 질문은 왜 당신이 그것에 대한 따옴표를 추가합니까? "$ {1 :-/ proc / $ {$} / fd / 0}"
Dagang

15
명령 행에 제공 한 파일 이름에 공백이있을 수 있습니다.
프리츠 G. 메 흐너

3
사용의 차이가 있나요 /proc/$$/fd/0/dev/stdin? 나는 후자가 더 흔하고 더 직관적 인 것처럼 보였다.
knowah

19
실수로 문자를 먹지 않도록 명령 에 추가 -r하는 것이 좋습니다 . 선행 및 후행 공백을 보존하는 데 사용 합니다. read\ while IFS= read -r line
mklement0

1
@NeDark : 궁금합니다. 방금 사용할 때에도 해당 플랫폼에서 작동하는지 확인했습니다. 또는 /bin/sh이외의 쉘을 사용하고 있습니까? bashsh
mklement0

119

아마도 가장 간단한 해결책은 병합 리디렉션 연산자로 stdin을 리디렉션하는 것입니다.

#!/bin/bash
less <&0

Stdin은 파일 디스크립터 0입니다. 위의 내용은 bash 스크립트로 파이프 된 입력을 less의 stdin으로 보냅니다.

파일 디스크립터 리디렉션에 대해 자세히 알아보십시오 .


1
나는 당신에게 더 많은 투표를 해주 었으면 좋겠다. 나는 몇 년 동안 이것을 찾고 있었다.
Marcus Downing

13
<&0이 상황에서 사용 하면 이점이 없습니다 -귀하의 예제는 그것과 함께 또는없이 작동합니다-bash 스크립트 내에서 호출하는 도구는 기본적으로 스크립트 자체와 동일한 stdin을 봅니다 (스크립트가 먼저 소비하지 않는 한).
mklement0

@ mkelement0 따라서 툴이 입력 버퍼의 절반을 읽는다면, 다음에 호출 할 툴이 나머지를 얻습니까?
Asad Saeeduddin

나는이 ... 우분투 16.04 수행 할 때 "("도움을 덜 --help) "파일 이름 누락"
OmarOthman

5
이 답변의 "파일에서"부분은 어디에 있습니까?
Sebastian

84

가장 간단한 방법은 다음과 같습니다.

#!/bin/sh
cat -

용법:

$ echo test | sh my_script.sh
test

변수에 stdin 을 할당하려면 다음을 사용 STDIN=$(cat -)하거나 단순히 STDIN=$(cat)연산자가 필요하지 않은 것처럼 ( @ mklement0 comment에 따라 ) 사용할 수 있습니다.


표준 입력 에서 각 줄을 구문 분석하려면 다음 스크립트를 시도하십시오.

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

파일 또는 stdin 에서 읽으려면 (인수가없는 경우) 다음으로 확장 할 수 있습니다.

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

노트:

-- read -r백 슬래시 문자를 특별한 방식으로 취급하지 마십시오. 각 백 슬래시를 입력 라인의 일부로 간주하십시오.

- 설정하지 않고 IFS의 기본적으로 시퀀스 SpaceTab라인의 시작과 끝에서 무시 (트리밍)된다.

- 사용 printf대신에이 echo라인이 단일 구성 할 때 빈 줄을 인쇄 피하기 위해 -e, -n또는 -E. 그러나 env POSIXLY_CORRECT=1 echo "$line"이를 사용 하여 외부 GNU echo를 실행 하는 해결 방법이 있습니다. 참조 : 어떻게 "-e"를 에코합니까?

참조 : 인수가 전달되지 않을 때 stdin을 읽는 방법은 무엇입니까? stackoverflow SE에서


당신은 단순화 할 [ "$1" ] && FILE=$1 || FILE="-"FILE=${1:--}있습니다. (Quibble : 환경 변수 와 이름 충돌을 피하기 위해 대문자로 된 모든 변수를 피하는 것이 좋습니다 .)
mklement0

천만에요; 실제로, ${1:--} 은 모든 쉘 POSIX - 같은 작업을해야하므로, POSIX 호환. 그러한 모든 쉘에서 작동하지 않는 것은 프로세스 대체 ( <(...))입니다. 예를 들어 bash, ksh, zsh에서는 작동하지만 대시에서는 작동하지 않습니다. 또한 실수로 문자를 먹지 않도록 명령 에 추가 -r하는 것이 좋습니다 . 선행 및 후행 공백을 보존하기 위해 추가 합니다. read\ IFS=
mklement0

4
사실 코드는 여전히 인해 나누기 echo라인이 구성되어있는 경우 : -e, -n또는 -E그것은 표시되지 않습니다. 이 문제를 해결하려면, 당신은 사용해야합니다 printf: printf '%s\n' "$line". 이전 편집에 포함하지 않았습니다…이 오류를 수정하면 편집 내용이 너무 자주 롤백됩니다 :(.
gniourf_gniourf

1
실패하지 않습니다. --첫 번째 인수가 다음 과 같은 경우 에는 쓸모가 없습니다.'%s\n'
gniourf_gniourf

1
당신의 대답은 괜찮습니다 (나는 더 이상 알고있는 버그 나 원치 않는 기능이 없음을 의미합니다). Perl처럼 여러 가지 주장을 다루지는 않지만. 실제로 여러 가지 주장을 다루고 싶다면 Jonathan Leffler의 훌륭한 답변을 작성하게 될 것입니다. 실제로 와 대신 사용 IFS=하고 나면 더 좋을 것 입니다 . . readprintfecho:)
gniourf_gniourf

19

나는 이것이 직접적인 방법이라고 생각합니다.

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

-

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

-

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5

4
이것은 stdin이나 파일 인수에서 읽는 포스터의 요구 사항에 맞지 않으며 stdin에서만 읽습니다.
nash

2
옆 내쉬의 유효한 이의 @두면 : readstdin에서 읽고 기본적으로 너무 없다, 필요< /dev/stdin.
mklement0

13

echo솔루션 IFS은 입력 스트림을 중단 할 때마다 새 줄을 추가 합니다. @fgm의 답변 은 약간 수정 될 수 있습니다 :

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"

"IFS가 입력 스트림을 중단 할 때마다 에코 솔루션이 새로운 라인을 추가합니다"라는 의미를 설명해 주시겠습니까? 경우에 당신은 언급 된 read행동의 '동안 read 않는 잠재적으로 문자로 여러 개의 토큰으로 분할합니다. 에 포함 된 경우 단일 변수 이름 만 지정하는 경우 단일 토큰 $IFS만 반환합니다 (그러나 기본적으로 트리밍 및 선행 및 후행 공백).
mklement0

@ mklement0 나는 당신의 행동에 100 % 동의 read하고 $IFS- echo그 자체는 -n깃발 없이 새로운 줄을 추가합니다 . "에코 유틸리티는 단일 피연산자 (` ') 문자로 구분되고 개행 문자 (`\ n') 문자로 구분 된 지정된 피연산자를 표준 출력에 기록합니다."
David Souther

알았다. 그러나 Perl 루프를 에뮬레이트하려면 다음 에 의해 추가 된 후행 이 필요 합니다 . Perl 's 행 읽기에서 끝나는 행을 포함 하지만 bash는 포함 하지 않습니다. 그러나 @gniourf_gniourf가 다른 곳에서 지적한 것처럼 더 강력한 접근 방식은 대신에 사용하는 것 입니다 . \necho$_ \nreadprintf '%s\n'echo
mklement0

8

질문의 Perl 루프 는 명령 행의 모든 파일 이름 인수 또는 파일이 지정되지 않은 경우 표준 입력에서 읽습니다 . 내가 본 답변은 파일이 지정되지 않은 경우 단일 파일 또는 표준 입력을 처리하는 것으로 보입니다.

종종 UUOC ( Unless Use of cat) 로 정확하게 정의 되기는하지만 cat작업에 가장 적합한 도구 인 경우 가 종종 있으며 다음 중 하나 일 수 있습니다.

cat "$@" |
while read -r line
do
    echo "$line"
done

이것의 유일한 단점은 하위 셸에서 실행되는 파이프 라인을 생성하므로 while루프의 변수 할당과 같은 항목 은 파이프 라인 외부에서 액세스 할 수 없습니다. 그 bash방법은 프로세스 대체입니다 .

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

이렇게하면 while루프가 기본 셸에서 실행되므로 루프에 설정된 변수는 루프 외부에서 액세스 할 수 있습니다.


1
여러 파일 에 대한 탁월한 지적 . 리소스 및 성능에 어떤 영향을 줄지 모르겠지만 bash, ksh 또는 zsh를 사용하지 않아 프로세스 대체를 사용할 수없는 경우 명령 대체를 사용하여 here-doc을 사용해보십시오 라인) >>EOF\n$(cat "$@")\nEOF. 마지막으로, quibble : Perl에서 수행 while IFS= read -r line하는 작업에 대한 더 나은 근사치입니다 while (<>)(앞뒤 공백을 유지하지만 Perl은 후행을 유지합니다 \n).
mklement0

4

OP에 주어진 코드를 가진 Perl의 행동은 인수를 전혀 또는 여러 개 취할 수 없으며 인수가 단일 하이픈 인 경우 -stdin으로 이해됩니다. 또한 파일 이름을 항상로 사용할 수 있습니다 $ARGV. 지금까지 주어진 답변 중 어느 것도 이러한 점에서 Perl의 행동을 모방하지 않습니다. 순수한 Bash 가능성이 있습니다. 트릭은 exec적절하게 사용하는 것 입니다.

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[ $1 = - ]] || exec < "$1"; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done

사용 가능한 파일 이름 $1 .

인수가 제공되지 않으면 인위적 -으로 첫 번째 위치 매개 변수로 설정 됩니다. 그런 다음 매개 변수를 반복합니다. 매개 변수가 아닌 경우 -파일 이름에서 표준 입력을로 리디렉션합니다 exec. 이 리디렉션이 성공하면 루프로 while루프합니다. 표준 REPLY변수를 사용하고 있으며이 경우 재설정 할 필요가 없습니다 IFS. 다른 이름을 원하면 재설정해야합니다 IFS(물론 원하지 않고 수행중인 작업을 알고 있지 않은 경우).

while IFS= read -r line; do
    printf '%s\n' "$line"
done

2

좀 더 정확하게...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file

2
나는 이것이 본질적으로 stackoverflow.com/a/6980232/45375에 대한 의견이라고 가정합니다 . 주석을 명시 적으로 작성하려면 명령에 IFS=and -r 를 추가 read하면 각 행을 수정하지 않고 읽을 수 있습니다 (앞뒤 공백 포함).
mklement0

2

다음 코드를 시도하십시오 :

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

1
수정하더라도 표준 입력 또는 여러 파일에서 읽히지 않으므로 질문에 대한 완전한 대답은 아닙니다. (답변이 처음 제출 된 후 3 년이 채 지나지 않아 몇 분 만에 두 개의 수정 사항을 확인하는 것도 놀라운 일입니다.)
Jonathan Leffler

@JonathanLeffler 같은 오래된 (그리고 정말 좋은) 대답을 편집 죄송합니다 ...하지만이 가난한 사람들을보고 참을 수 read없이 IFS=하고 -r, 가난한 사람들 $line의 건강에 따옴표없이.
gniourf_gniourf

1
@gniourf_gniourf : 나는 그 read -r표기법을 싫어한다 . IMO, POSIX가 잘못했습니다. 이 옵션은 후행 백 슬래시에 대한 특별한 의미를 활성화해야하며 비활성화하지 않아야합니다. 따라서 POSIX가 존재하기 전의 기존 스크립트 -r가 생략되어 중단되지 않습니다 . 그러나 POSIX 셸 및 유틸리티 표준의 가장 초기 버전 인 IEEE 1003.2 1992의 일부 였지만 그 이후에도 추가로 표시되었으므로 오랫동안 사라질 기회가 없습니다. 내 코드를 사용하지 않기 때문에 문제가 발생하지 않았습니다 -r. 나는 운이 좋을 것입니다. 이것에 대해서는 무시하십시오.
Jonathan Leffler

1
@JonathanLeffler 나는 그것이 -r표준이어야 한다는 것에 정말로 동의합니다 . 사용하지 않으면 문제가 발생할 가능성이 적다는 데 동의합니다. 그러나 깨진 코드는 깨진 ​​코드입니다. 내 편집은 $line따옴표를 잘못 놓친 가난한 변수 에 의해 처음 시작되었습니다 . 내가있는 read동안 나는 고쳤다 . echo롤백되는 일종의 편집이므로 수정하지 않았습니다 . :(.
gniourf_gniourf

1

코드 ${1:-/dev/stdin}는 첫 번째 주장을 이해할 것입니다.

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done

1

이 답변 중 어느 것도 받아 들일 수 없습니다. 특히, 허용 된 답변은 첫 번째 명령 줄 매개 변수 만 처리하고 나머지는 무시합니다. 에뮬레이트하려는 Perl 프로그램은 모든 명령 행 매개 변수를 처리합니다. 따라서 허용 된 답변은 질문에 대답조차하지 않습니다. 다른 답변은 bash 확장을 사용하고 불필요한 'cat'명령을 추가하거나 입력을 출력으로 에코하는 간단한 경우에만 작동하거나 불필요하게 복잡합니다.

그러나 나는 그들이 나에게 아이디어를 줬기 때문에 그들에게 약간의 신용을 주어야한다. 완전한 대답은 다음과 같습니다.

#!/bin/sh

if [ $# = 0 ]
then
        DEFAULT_INPUT_FILE=/dev/stdin
else
        DEFAULT_INPUT_FILE=
fi

# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
        while IFS= read -r LINE
        do
                # Do whatever you want with LINE here.
                echo $LINE
        done < "$FILE"
done

1

위의 모든 답변을 결합하고 내 요구에 맞는 쉘 함수를 만들었습니다. 이것은 내 두 Windows10 컴퓨터의 cygwin 터미널에서 공유 폴더가있는 곳입니다. 다음을 처리 할 수 ​​있어야합니다.

  • cat file.cpp | tx
  • tx < file.cpp
  • tx file.cpp

특정 파일 이름이 지정된 경우 복사하는 동안 동일한 파일 이름을 사용해야합니다. 입력 데이터 스트림이 통해 파이프 된 경우 시간 분과 초를 가진 임시 파일 이름을 생성해야합니다. 공유 된 메인 폴더에는 요일의 하위 폴더가 있습니다. 이것은 조직을위한 것입니다.

보라, 나의 필요를위한 궁극적 인 대본 :

tx ()
{
  if [ $# -eq 0 ]; then
    local TMP=/tmp/tx.$(date +'%H%M%S')
    while IFS= read -r line; do
        echo "$line"
    done < /dev/stdin > $TMP
    cp $TMP //$OTHER/stargate/$(date +'%a')/
    rm -f $TMP
  else
    [ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
  fi
}

이것을 더 최적화 할 수있는 방법이 있다면 알고 싶습니다.


0

다음은 표준 sh( dashDebian에서 테스트 됨)에서 작동하며 읽을 수는 있지만 맛의 문제입니다.

if [ -n "$1" ]; then
    cat "$1"
else
    cat
fi | commands_and_transformations

세부 사항 : 첫 번째 매개 변수가 비어 있지 cat않으면 해당 파일, 그렇지 않으면 cat표준 입력입니다. 그런 다음 전체 if명령문 의 출력 이로 처리됩니다 commands_and_transformations.


진정한 해결책을 가리 키기 때문에 최상의 답변을 IMHO하십시오 cat "${1:--}" | any_command. 쉘 변수를 읽고 에코하는 것은 작은 파일에는 효과가 있지만 확장 성이 떨어집니다.
Andreas Spindler

[ -n "$1" ]로 단순화 될 수있다[ "$1" ] .
agc

0

이것은 터미널에서 사용하기 쉽습니다.

$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3

-1

어때요?

for line in `cat`; do
    something($line);
done

의 출력이 cat명령 행에 배치됩니다. 명령 행은 최대 크기입니다. 또한 이것은 한 줄씩 읽지 않고 한 단어 씩 읽습니다.
Notinlist
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.