対話型シェルスクリプトで、コマンドの出力を次のようにファイルに保存したい。
$ echo "Hello, World" > test.txt
しかし、test.txtがすでに存在する場合は上書きされないようにしたい。そこで、cpインタラクティブオプション(cp -i) を使用して、対象ファイルが存在するかどうかを確認します。私はこれを試しました:
$ echo -n "Hello" | cp -i /dev/stdin test.txt
これは、test.txt がまだ存在しない場合は test.txt に「Hello」を書き込みますが、test.txt が存在する場合はコピーを中止します。これは、cp が上書きするかどうかの答えをそのパイプからのみ読み取るためです。
しかし、この
$ cp -i <(echo "World") test.txt
cp: overwrite 'test.txt'? y
'/proc/self/fd/11' -> 'test.txt'
意図したとおりに動作します。cp はサブプロセスのファイル記述子をソースとして取得し、これが test.txt に cp されるようですが、その理由はまったくわかりません。
何かアイデアや説明、あるいはより良い方法はありますか?
答え1
代わりに別のファイル記述子を使用する/dev/stdin
echo Hello | { cp -i /dev/fd/5 test.txt 5<&0 </dev/tty; }
または、yes(1) 経由でスクリプト化可能
{ echo Hello | { cp -i /dev/fd/5 test.txt 5<&0 <&4; }; } 4<&0
どちらのバージョンでも、追加の{ ... }
aroundはcp -i ...
zshでのみ必要であり、非標準のマルチOS特徴。
これは、Linux + ksh93 の組み合わせを除く、標準シェルとファイルをサポートするシステムであればどれでも動作するはずです。Linux /dev/fd/N
+ ksh93 の組み合わせは、ksh93 が Unix ソケットでパイプを実装しており、Linux ではこれを介して開くことができない/dev/fd/N
(echo | cat /dev/stdin
動作すらしない) ため動作しません。
の代わりに をそこに置いた場合、実際echo -n "Hello" | cp -i /dev/stdin test.txt
にファイルが上書きされます。これが に頼るべきではない理由の 1 つです。"Hello"
"You suck!"
cp -i
答え2
などのシェルは、bash
リダイレクトによるファイルの上書きをすでに防ぐことができます。
set -o noclobber
echo hello >world
echo hello >world
-bash: world: cannot overwrite existing file
上書きする前にユーザーに確認したい場合は、設定の代わりに次のようなものを使用しますnoclobber
。
#!/bin/bash
# Define function sureYN [<prompt>] <target>
sureYN() {
local prompt="$1" target="$2" yn='y'
[[ -z "$target" ]] && { target="$prompt"; prompt="Overwrite $target"; }
[[ -f "$target" && -t 0 ]] && read -p "$prompt (y/n)? " yn >&2
[[ "$yn" =~ ^[Yy] ]]
}
sureYN /tmp/first && cp -fp /etc/hosts /tmp/first
sureYN "Can I replace /tmp/second" /tmp/second && echo this is the second >/tmp/second