Programing

마지막 줄이 누락 된 쉘 스크립트 읽기

lottogame 2020. 12. 11. 07:39
반응형

마지막 줄이 누락 된 쉘 스크립트 읽기


나는 약간의 통찰력을 얻고 싶었던 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:

  1. There's no need to use cat with while loop. while ...;do something;done<file is enough.
  2. Don't read lines with for.

When using while loop to read lines:

  1. Set the IFS properly (you may lose indentation otherwise).
  2. 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

반응형