마지막 줄이 누락 된 쉘 스크립트 읽기
나는 약간의 통찰력을 얻고 싶었던 bash 쉘 스크립트에 이상한 문제가 있습니다.
우리 팀은 파일의 줄을 반복하고 각 줄의 내용을 확인하는 스크립트를 작성하고 있습니다. 서로 다른 스크립트를 함께 시퀀스하는 자동화 된 프로세스를 통해 실행할 때 마지막 줄이 보이지 않는 버그가있었습니다.
파일의 행을 반복하는 데 사용되는 코드 (저장된 이름 DATAFILE
은
cat "$DATAFILE" | while read line
명령 줄에서 스크립트를 실행하면 마지막 줄을 포함하여 파일의 모든 줄을 볼 수 있습니다. 그러나 자동화 된 프로세스 (문제의 스크립트 바로 이전에 DATAFILE을 생성하는 스크립트를 실행하는 스크립트를 실행)에 의해 실행되면 마지막 줄이 표시되지 않습니다.
다음을 사용하여 줄을 반복하도록 코드를 업데이트했으며 문제가 해결되었습니다.
for line in `cat "$DATAFILE"`
참고 : DATAFILE에는 파일 끝에 새 줄이 기록되지 않습니다.
내 질문은 두 부분입니다 ... 왜 마지막 줄이 원래 코드에 표시되지 않고 이것이 변경되어 차이가 발생합니까?
마지막 줄이 보이지 않는 이유는 다음과 같습니다.
- 파일을 쓰는 이전 프로세스는 파일 설명자를 닫기 위해 프로세스를 종료하는 데 의존했습니다.
- 문제 스크립트는 이전 프로세스가 "종료"되었지만 시스템이 파일 설명자를 자동으로 닫을 수있을만큼 충분히 "종료 / 정리"되지 않았을 정도로 충분히 빨리 파일을 시작하고 열었습니다.
즉, 쉘 스크립트에 2 개의 명령이있는 경우 첫 번째 명령은 스크립트가 두 번째 명령을 실행할 때 완전히 종료되어야합니다.
질문, 특히 첫 번째 질문에 대한 통찰력은 대단히 감사하겠습니다.
C 표준에 따르면 텍스트 파일은 개행 문자로 끝나야하며 그렇지 않으면 마지막 개행 이후의 데이터가 제대로 읽히지 않을 수 있습니다.
ISO / IEC 9899 : 2011 §7.21.2 스트림
텍스트 스트림은 줄로 구성된 순서가 지정된 문자 시퀀스로, 각 줄은 0 개 이상의 문자와 종료 줄 바꾸기 문자로 구성됩니다. 마지막 줄에 종료 줄 바꾸기 문자가 필요한지 여부는 구현에 따라 정의됩니다. 호스트 환경에서 텍스트를 표현하기위한 다른 규칙을 따르기 위해 입력 및 출력에서 문자를 추가, 변경 또는 삭제해야 할 수 있습니다. 따라서 스트림의 문자와 외부 표현의 문자간에 일대일 대응이 필요하지 않습니다. 텍스트 스트림에서 읽은 데이터는 다음 경우에만 해당 스트림에 이전에 기록 된 데이터와 반드시 동일하게 비교됩니다. 데이터는 인쇄 문자와 제어 문자 가로 탭 및 줄 바꾸기로만 구성됩니다. 개행 문자 바로 앞에 공백 문자가 없습니다. 마지막 문자는 개행 문자입니다. 개행 문자 바로 앞에 기록되는 공백 문자를 읽을 때 나타나는지 여부는 구현에 따라 정의됩니다.
파일 끝에서 줄 바꿈이 누락되어 bash
(또는 Unix 셸) 문제가 발생하지는 않지만 재현 가능한 문제인 것 같습니다 ( $
이 출력의 프롬프트).
$ echo xxx\\c
xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y
$ cat y
abc
def
ghi
xxx$
$ while read line; do echo $line; done < y
abc
def
ghi
$ bash -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ ksh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ zsh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ for line in $(<y); do echo $line; done # Preferred notation in bash
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done # UUOC Award pending
abc
def
ghi
xxx
$
또한 bash
-Korn 쉘 ( ksh
)로 제한되지 않으며 zsh
이와 같이 작동합니다. 나는 살고 배운다. 문제를 제기 해 주셔서 감사합니다.
위의 코드에서 볼 수 있듯이 cat
명령은 전체 파일을 읽습니다. 이 for line in `cat $DATAFILE`
기술은 모든 출력을 수집하고 임의의 공백 시퀀스를 단일 공백으로 바꿉니다 (파일의 각 행에 공백이 없다고 결론을 내립니다).
Mac OS X 10.7.5에서 테스트되었습니다.
POSIX는 무엇을 말합니까?
POSIX read
명령 사양은 다음과 같습니다.
읽기 유틸리티는 표준 입력에서 한 줄을 읽어야합니다.
기본적으로
-r
옵션을 지정 하지 않으면 <백 슬래시>가 이스케이프 문자로 작동합니다. 이스케이프 처리되지 않은 <백 슬래시>는 <newline>을 제외하고 다음 문자의 리터럴 값을 유지합니다. <newline>이 <backslash> 뒤에 오면 읽기 유틸리티는 이것을 줄 연속으로 해석합니다. <백 슬래시> 및<newline>
입력을 필드로 분할하기 전에 제거해야합니다. 다른 모든 이스케이프 처리되지 않은 <백 슬래시> 문자는 입력을 필드로 분할 한 후 제거됩니다.표준 입력이 터미널 장치이고 호출 셸이 대화 형인 경우 read는
-r
옵션이 지정 되지 않은 경우 <백 슬래시> <newline>으로 끝나는 입력 줄을 읽을 때 연속 줄을 입력하라는 메시지를 표시 합니다.종료 <newline> (있는 경우) 은 입력에서 제거되고 결과는 매개 변수 확장의 결과에 대한 쉘에서와 같이 필드로 분할됩니다 (필드 분할 참조). [...]
'(있는 경우)'(인용문에 강조가 추가됨)에 유의하십시오! 개행이 없으면 결과를 읽어야 할 것 같습니다. 반면에 다음과 같이 말합니다.
STDIN
표준 입력은 텍스트 파일이어야합니다.
그런 다음 개행으로 끝나지 않는 파일이 텍스트 파일인지 여부에 대한 토론으로 돌아갑니다.
그러나 동일한 페이지 문서에 대한 근거 :
표준 입력은 텍스트 파일이어야하므로 항상 <newline> (빈 파일
-r
이 아닌 경우)으로 끝나지만 옵션을 사용하지 않을 때 연속 행을 처리 하면 입력이 다음으로 끝나지 않을 수 있습니다. <개행>. 입력 파일의 마지막 줄이 <backslash> <newline>으로 끝나는 경우에 발생합니다. 이러한 이유로 설명에서 "종료 <newline> (있는 경우)은 입력에서 제거되어야 함"에서 "있는 경우"가 사용됩니다. 표준 입력이 텍스트 파일이되는 것은 요구 사항의 완화가 아닙니다.
그 근거는 텍스트 파일이 개행으로 끝나야한다는 것을 의미해야합니다.
텍스트 파일의 POSIX 정의는 다음과 같습니다.
3.395 텍스트 파일
0 개 이상의 행으로 구성된 문자를 포함하는 파일입니다. 행은 NUL 문자를 포함하지 않으며 <newline> 문자를 포함하여 길이가 {LINE_MAX} 바이트를 초과 할 수 없습니다. POSIX.1-2008은 텍스트 파일과 이진 파일을 구분하지 않지만 (ISO C 표준 참조) 많은 유틸리티는 텍스트 파일에서 작동 할 때 예측 가능하거나 의미있는 출력 만 생성합니다. 이러한 제한이있는 표준 유틸리티는 항상 STDIN 또는 INPUT FILES 섹션에 "텍스트 파일"을 지정합니다.
이것은 'ends with a <newline>'을 직접 규정하지 않지만 C 표준을 따릅니다.
'no terminal newline'문제에 대한 해결책
Gordon Davisson 의 대답을 참고하십시오 . 간단한 테스트는 그의 관찰이 정확하다는 것을 보여줍니다.
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
따라서 그의 기술 :
while read line || [ -n "$line" ]; do echo $line; done < y
또는:
cat y | while read line || [ -n "$line" ]; do echo $line; done
will work for files without a newline at the end (at least on my machine).
I'm still surprised to find that the shells drop the last segment (it can't be called a line because it doesn't end with a newline) of the input, but there might be sufficient justification in POSIX to do so. And clearly it is best to ensure that your text files really are text files ending with a newline.
According to the POSIX spec for the read command, it should return a nonzero status if "End-of-file was detected or an error occurred." Since EOF is detected as it reads the last "line", it sets $line
and then returns an error status, and the error status prevents the loop from executing on that last "line". The solution is easy: make the loop execute if the read command succeeds OR if anything was read into $line
.
while read line || [ -n "$line" ]; do
Adding some additional info:
- There's no need to use
cat
with while loop.while ...;do something;done<file
is enough. - Don't read lines with
for
.
When using while loop to read lines:
- Set the
IFS
properly (you may lose indentation otherwise). - You should almost always use the -r option with read.
with meeting the above requirements a proper while loop will look like this:
while IFS= read -r line; do
...
done <file
And to make it work with files without a newline at end (reposting my solution from here):
while IFS= read -r line || [ -n "$line" ]; do
echo "$line"
done <file
Or using grep
with while loop:
while IFS= read -r line; do
echo "$line"
done < <(grep "" file)
Use sed to match the last line of a file, which it will then append a newline if one does not exist and have it do an inline replacement of the file:
sed -i '' -e '$a\' file
The code is from this stackexchange link
Note: I have added empty single quotes to -i ''
because, at least in OS X, -i
was using -e
as a file extension for the backup file. I would have gladly commented on the original post but lacked 50 points. Perhaps this will gain me a few in this thread, thanks.
I tested this in command line
# create dummy file. last line doesn't end with newline
printf "%i\n%i\nNo-newline-here" >testing
Test with your first form (piping to while-loop)
cat testing | while read line; do echo $line; done
이것은 마지막 줄을 놓친다 read
. 이는 개행으로 끝나는 입력 만 받기 때문에 의미 가있다.
두 번째 형식으로 테스트 (명령 대체)
for line in `cat testbed1` ; do echo $line; done
이것은 마지막 줄도 가져옵니다.
read
줄 바꿈으로 끝나는 경우에만 입력을 받기 때문에 마지막 줄을 놓친 것입니다.
반면에 두 번째 형태에서는
`cat testing`
형태로 확장
line1\nline2\n...lineM
IFS를 사용하여 셸에 의해 여러 필드로 분리되므로
line1 line2 line3 ... lineM
그것이 당신이 여전히 마지막 줄을 얻는 이유입니다.
p / s : 내가 이해하지 못하는 것은 첫 번째 양식이 작동하는 방법입니다 ...
해결 방법으로, 텍스트 파일에서 읽기 전에 파일에 줄 바꿈을 추가 할 수 있습니다.
echo "\n" >> $file_path
이렇게하면 이전에 파일에 있던 모든 행을 읽을 수 있습니다.
I had a similar issue. I was doing a cat of a file, piping it to a sort and then piping the result to a 'while read var1 var2 var3'. ie: cat $FILE|sort -k3|while read Count IP Name do The work under the "do" was an if statement that identified changing data in the $Name field and based on change or no change did sums of $Count or printed the summed line to the report. I also ran into the issue where I couldnt get the last line to print to the report. I went with the simple expedient of redirecting the cat/sort to a new file, echoing a newline to that new file and THEN ran my "while read Count IP Name" on the new file with successful results. ie: cat $FILE|sort -k3 > NEWFILE echo "\n" >> NEWFILE cat NEWFILE |while read Count IP Name do Sometimes the simple, inelegant is the best way to go.
참고URL : https://stackoverflow.com/questions/12916352/shell-script-read-missing-last-line
'Programing' 카테고리의 다른 글
암호 솔트를 "솔트"라고하는 이유는 무엇입니까? (0) | 2020.12.11 |
---|---|
Rails Devise : 현재 로그인 한 사용자의 객체를 얻습니까? (0) | 2020.12.11 |
SQS 대 RabbitMQ (0) | 2020.12.11 |
Angular 2 라우터 이벤트 리스너 (0) | 2020.12.11 |
CURL을 통해 모든 기기에 Firebase 알림을 어떻게 보내나요? (0) | 2020.12.11 |