如果我们修改文本文件,bash while 循环读取变量“line”是否会更新自身?

如果我们修改文本文件,bash while 循环读取变量“line”是否会更新自身?

举例来说,我有一个名为 hello.txt 的文本文件,其中包含“Hello”。如果我有一个 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)                           = ?

基本上任何read遇到 EOF 都会导致while循环终止。如果您在脚本中替换echo "$line" > hello.txt为 ,echo "$line" >> hello.txt它将运行直到存储填满,因为每次read运行时在文件末尾之前都会有更多内容。

相关内容