텍스트 파일을 수정하면 변수 "line"을 읽는 bash while 루프가 자동으로 업데이트됩니까?

텍스트 파일을 수정하면 변수 "line"을 읽는 bash while 루프가 자동으로 업데이트됩니까?

예를 들어 "Hello"가 포함된 hello.txt라는 텍스트 파일이 있습니다. 읽을 수 있는 while 루프가 있는 경우:

while IFS= read -r line
do
    echo $line > hello.txt # whatever that inserts/ edits something in the text file
done < hello.txt

while 루프 읽기가 변경되거나 무한 루프가 발생합니까? 아니면 while 루프가 텍스트 파일의 변경 사항을 읽지 못합니까? 업데이트/수정된 텍스트 파일을 강제로 읽도록 할 수 있나요?

답변1

루프의 입력과 read출력이 동일한 파일에 연결됩니다. 그렇습니다.echo

이 특정한 경우에는 출력 리디렉션을 설정할 때마다 echo개별 호출마다 파일이 잘립니다. 입력의 읽기 위치는 마지막 읽기 라인 바로 뒤의 원래 위치로 유지됩니다. 여기서는 정확히 동일한 줄을 인쇄하므로 읽기 위치는 새 EOF에 있고 루프는 한 번의 반복 후에 종료됩니다. 파일의 원래 첫 번째 줄을 파일의 유일한 내용으로 유지합니다.


그러나 더 긴 텍스트를 출력하는 경우 루프는 이전에 출력된 내용의 일부를 읽을 수 있습니다.

예를 들어 고려하십시오.

$ cat hello.txt
abc
def
ghi
$ cat test.sh
while IFS= read -r line
do
    echo "$line"  # to terminal, to see what is read
    echo "something something $line" > "$1"
done < "$1"

이제 스크립트를 실행하면 hello.txt다음 줄이 남습니다.

something something g something abc

첫 번째 라인 뒤에는 파일이 포함되어 something something abc있으며 읽기 위치는 첫 번째 라인에 있습니다 t( abc및 개행 문자를 읽기 때문입니다). 읽기 thing something abc및 인쇄를 반복합니다 something something thing something abc(파일 자르기). 읽기 위치는 여전히 중간에 있으므로 최종적으로 EOF에 도달할 때까지 루프가 계속 반복됩니다.

read특히 읽기 위치는 정확히 줄 끝에서 유지됩니다 . 이는 예를 들어 (표준 준수) 와 동일 head -n1하지만 다른 많은 유틸리티는 전체 블록을 읽고 읽기 위치를 더 짧은 입력을 위해 원본 파일의 끝에 남겨 둡니다. 여기서 결과가 변경됩니다.


반면에 출력 리디렉션을 로 바꾸면 >> file모든 쓰기가 파일에 추가되고 읽기 위치는 결코 끝에 도달하지 않습니다(루프는 반복당 한 줄만 읽고 쓰기 때문에). 무한 루프가 발생합니다.


다시 말하지만 만약 우리가제거하다루프 내부의 출력 파일은 리디렉션 전에 모든 것이 변경됩니다.

while IFS= read -r line
do
    rm "$1"
    echo "$line"  # to terminal, to see what is read
    echo "something something $line" > "$1"
done < "$1"

이제 더 이상 이름이 없기 때문에 출력 리디렉션에서 동일한 파일에 액세스할 수 없습니다. 새 파일이 생성됩니다. 읽기 파일 핸들은 여전히 ​​파일에 연결되어 있습니다. 더 이상 열려 있지 않은 경우에만 결국 삭제됩니다. 원본 버전에서 이것을 실행하면 hello.txt새로운 버전이 남습니다 hello.txt.

something something ghi

이제 출력 파일이 입력과 독립적이므로 루프는 입력을 끝까지 읽습니다. 각 반복마다 삭제되고 다시 생성됩니다.


루프 반복마다 파일을 삭제하는 것은 그다지 유용하지 않지만, 한 번만 삭제된 경우 리디렉션을 약간 이동해야 합니다. 그러면 전체 파일이 반복되고 모든 줄에 다음이 붙은 동일한 이름의 새 파일이 남습니다 something something.

exec < "$1"  # input redirection for the whole shell script
rm "$1"
exec > "$1"  # symmetrically, output, creating a new file
             # with the same name
while IFS= read -r line
do
    echo "$line"  >&2 # to terminal via stderr, to see what is read
    echo "something something $line"
done

답변2

이를 시험해보고 몇 가지 디버깅 정보를 살펴보겠습니다. 저는 커널 호출에 익숙하지 않지만 경험이 많은 사람이 실수를 수정할 수 있기를 바랍니다.

$ cd "$(mktemp --directory)"
$ cat > test.bash <<'EOF'
> while IFS= read -r line
> do
>     echo "$line" > hello.txt
> done < hello.txt
> EOF
$ echo foo > hello.txt
$ strace bash --noprofile --norc test.bash 
[skipping the script setup for clarity, and annotating the rest]
# Open hello.txt for reading as file descriptor 3
openat(AT_FDCWD, "hello.txt", O_RDONLY) = 3
fcntl(0, F_GETFD)                       = 0
fcntl(0, F_DUPFD, 10)                   = 10
fcntl(0, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
# Duplicate FD 3 as FD 0
dup2(3, 0)                              = 0
# Close FD 3
close(3)                                = 0
ioctl(0, TCGETS, 0x7ffc7a087960)        = -1 ENOTTY (Inappropriate ioctl for device)
# Go to start of FD 0
lseek(0, 0, SEEK_CUR)                   = 0
# Read up to max 128 bytes from FD 0. Four bytes read, which is the full file.
# `read` strips the newline, so $line ends up containing just "foo"
read(0, "foo\n", 128)                   = 4
# Open hello.txt for writing as FD 3
openat(AT_FDCWD, "hello.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 11
fcntl(1, F_GETFD)                       = 0
fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
# Duplicate FD 3 as FD 1
dup2(3, 1)                              = 1
# Close FD 3
close(3)                                = 0
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
# Write what we read plus a newline to FD 1
write(1, "foo\n", 4)                    = 4
dup2(11, 1)                             = 1
fcntl(11, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(11)                               = 0
ioctl(0, TCGETS, 0x7ffc7a087960)        = -1 ENOTTY (Inappropriate ioctl for device)
# Go to current position in FD 0 (index 4, as returned by the previous `read`)
lseek(0, 0, SEEK_CUR)                   = 4
# Try to read again from FD 0. Encounters end of file (return value 0).
read(0, "", 128)                        = 0
# Shut down
dup2(10, 0)                             = 0
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
read(255, "", 129)                      = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
exit_group(0)                           = ?

기본적으로 EOF가 발생하면 루프가 종료 read됩니다 . while스크립트를 교체하면 echo "$line" > hello.txt실행될 때마다 파일이 끝나기 전에 더 많은 내용이 있기 echo "$line" >> hello.txt때문에 저장소가 가득 찰 때까지 실행됩니다 .read

관련 정보