ファイルを独自に転送するためのスポンジの標準的な代替案はありますか?

ファイルを独自に転送するためのスポンジの標準的な代替案はありますか?

私はしばしば次のことをしたいと思います。

 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ユーティリティは標準ではありませんが、一般的に使用できます。

上記の関数は、次に説明する動作を模倣します。手動spongeDebianのパッケージ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

関連情報