私はしばしば次のことをしたいと思います。
cat file | command > file
(これは明らかに動作しません)。私が見た唯一の解決策はsponge
次のとおりです。
cat file | command | sponge file
残念ながらsponge
私はそれを使用することはできません(また、それをインストールしたり他のパッケージをインストールしたりすることはできません)。
毎回複数のコマンド(一時ファイルへのパイプ、元のファイルへの再パイプ、一時ファイルの削除)に分割せずにこれを行うためのより標準的で迅速な方法はありますか?tee
たとえば、これを試してみましたが、うまくいくようですが、一貫して安全なソリューションですか?
答え1
シェル関数の置換sponge
:
mysponge () (
append=false
while getopts 'a' opt; do
case $opt in
a) append=true ;;
*) echo error; exit 1
esac
done
shift "$(( OPTIND - 1 ))"
outfile=$1
tmpfile=$(mktemp "$(dirname "$outfile")/tmp-sponge.XXXXXXXX") &&
cat >"$tmpfile" &&
if "$append"; then
cat "$tmpfile" >>"$outfile"
else
if [ -f "$outfile" ]; then
chmod --reference="$outfile" "$tmpfile"
fi
if [ -f "$outfile" ]; then
mv "$tmpfile" "$outfile"
elif [ -n "$outfile" ] && [ ! -e "$outfile" ]; then
cat "$tmpfile" >"$outfile"
else
cat "$tmpfile"
fi
fi &&
rm -f "$tmpfile"
)
このmysponge
シェル関数は、標準入力で使用可能なすべてのデータを一時ファイルに渡します。
すべてのデータが一時ファイルにリダイレクトされると、収集されたデータは関数パラメータで指定されたファイルにコピーされます。データがそうでない場合追加ファイルに(つまり、-a
使用されていない)、指定された出力ファイル名が既存の汎用ファイルを参照している場合、そのファイルが存在しない場合はこれが行われますmv
(ファイルが既存の一般ファイルの場合は、まずGNUモードを使用してファイルをコピーします。 )一時ファイルに送信されますchmod
。)出力が通常のファイル(名前付きパイプ、stdoutなど)ではない場合、データはcat
。
コマンドラインにファイルが提供されていない場合、収集されたデータは標準出力に送信されます。
最後に、一時ファイルが削除されます。
関数の各ステップは、前のステップの成功した完了に依存します。コマンドが失敗した場合(重要なデータを含む可能性がある)、一時ファイルを削除しようとする試みは行われません。
指定されたファイルが存在しない場合は、ユーザーのデフォルト権限などを使用して作成され、標準入力から到着したデータがここに書き込まれます。
このmktemp
ユーティリティは標準ではありませんが、一般的に使用できます。
上記の関数は、次に説明する動作を模倣します。手動sponge
Debianのパッケージmoreutils
。
代わりに使用することは実行可能なオプションではtee
ありません。sponge
あなたはそれを試してみて、それがあなたに効果があるようだと言いました。うまくいかないかもしれません。これは、パイプラインでコマンドを開始するタイミング(同じ時間に開始)と入力データファイルのサイズによって異なります。
tee
以下は、使用法が機能しない場合を示す例です。
元のファイルは200000バイトですが、パイプされた後は32KiBに切り捨てられます(おそらく私のシステムのいくつかのバッファサイズに対応します)。
$ yes | head -n 100000 >hello
$ ls -l hello
-rw-r--r-- 1 kk wheel 200000 Jan 10 09:45 hello
$ cat hello | tee hello >/dev/null
$ ls -l hello
-rw-r--r-- 1 kk wheel 32768 Jan 10 09:46 hello
答え2
Perlを必要とする短いbashスクリプトがあります。
https://github.com/ildar-shaimordanov/perl-utils#sponge
2番目のスクリプトはmoreutilsのバージョンをすぐに置き換える必要があります。
スタンドアロンのPerlスクリプトバージョンもあります。
答え3
function wf() {
#create a temporary file
local tmpf="${1}_$(< /dev/urandom tr -dc A-Za-z0-9 | head -c16)"
#redirect the result
cat > $tmpf
#replace the original file
mv -f $tmpf "${1}"
}
次に、この機能を使用します。
grep "error" messages.log | wf messages.log
答え4
パリを追い出すために大砲を使用するのはなぜですか?考えられる解決策は次のとおりです。
stdin-to-file () {
local function_name="${FUNCNAME[0]}"
local tmp_file
local append=false
local exit_code=0
for (( i=1; i<=$#; i++ )); do
if [[ ${!i} = -- ]]; then
set -- "${@:1:i-1}" "${@:i+1}"
break
fi
if [[ ${!i} = -a || ( --append = ${!i}* && $(expr length "${!i}") -ge 3 ) ]]; then
append=true
set -- "${@:1:i-1}" "${@:i+1}"
((i--))
continue
fi
done
if [[ $# -ne 1 || -t 0 ]]; then
echo "$function_name: Wrong number of arguments or missing stdin." >&2
return 1
fi
tmp_file="$(mktemp "/tmp/$(basename -- "$1")-XXXXXXXXXXXX")" &&
cat > "$tmp_file" &&
if $append; then
cat "$tmp_file" >> "$1"
else
cat "$tmp_file" > "$1"
fi ||
exit_code=$?
rm -f -- "$tmp_file"
if [[ $exit_code != 0 ]]; then
echo "$function_name: An error has occurred." >&2
fi
return $exit_code
}
それから:
cat file | command | stdin-to-file file
追加:
cat file | command | stdin-to-file -a file
または:
cat file | command | stdin-to-file --append file