
たとえば、「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にあり、ループは1回の反復後に終了します。ファイルの最初の最初の行をファイルの唯一の内容として保持します。
ただし、長いテキストを出力すると、ループは以前に出力された内容の一部を読み取ることができます。
たとえば、考えてみましょう。
$ 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
すべての書き込みがファイルに追加され、読み取り位置は決して終わりに達しません(ループは繰り返しごとに1行だけ読み書きするため)。無限ループが発生します。
繰り返しますが、削除するループ内の出力ファイルは、リダイレクト前にすべてが変更されます。
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