単一ファイルをパイプライン全体の入出力として処理する[冗長]

単一ファイルをパイプライン全体の入出力として処理する[冗長]

おやすみなさい、

いくつかのパイプコマンドを使用してファイルの内容をフィルタリングし、結果を同じファイルに書き換えたいと思います。私が書いたようにできないことを知っています。待つ…

これは私のbashスクリプトです。

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

だから代わりに、プロセス置換をうまく使うことができると思います。それから私は次のように書きました。

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

これも何も解決しませんでした。一時ファイルなど、どこかに入力ファイルの内容を「保存」するためのプロセスを置き換えたいと思います。プロセスの置き換えも理解できないようです。

「現在の場所」バージョンのスレッドを読みましたが、その記事では、またはいくつかのsed -iバイナリの特別なオプションを強調しましたが、sort -o一般的な解決策が必要です(すべてのパイプラインコマンドを満たす必要があることを意味します)。

まず、「標準パイプライン方式」がこれを行わないのはなぜですか?その下では何が起こっていますか?:/問題をどのように解決するのですか?誰でもお願いします説明する私に何が起こりましたか?

ありがとうございます。

答え1

すでに述べたように、スポンジは以下に由来します。その他のユーティリティ大きい。 moreutils 依存関係を避けるために、このスクリプトをモックに使用します。

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

次のように使用できます。

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

コマンドが開始される前にリダイレクトが発生し、出力リダイレクトは出力ファイルを切り捨てるため、単純な出力リダイレクトを使用してこれを行うことはできません。

つまり、grep(パイプラインの最初の単純なコマンド)が起動すると、最後のリダイレクトですでに入力/出力ファイルが切り捨てられています。

私が知っている限り、実際に内部編集を実行する標準のUNIXユーティリティはありません。sed -iシミュレーションには一時ファイルのみを使用してください。これは、パイプラインステップが失敗した場合に実際の内部フィルタリングがファイルを簡単に破損させる可能性があるためです。

以下で何が起こっているのかについては、両方ともシステムパイプを|使用<()し、一度に1つのバッファをIOに渡します。このメカニズムは一時ファイル(実際のファイルシステムファイルではない)を生成せず、入力全体を一度にメモリに保持するのを防ぎます。

答え2

同じファイルから入力と出力が必要な場合は、試してみることができますスポンジ。説明によると、

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

だからあなたは次のようなsed '...' file | grep '...' | sponge [-a] file入力を持つことができます文書そして同じように出力文書


一方、一時ファイルを使用することも、同じファイルを入力と出力に使用するのに最適な方法です。次のように一時ファイルを初期化できます。

tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want

これにより、スクリプトが実行されるディレクトリに拡張子「XXXX」の「tempFile」という一時ファイルが作成されます。ここで、xは現在のプロセス番号とランダム文字の組み合わせ(tempFile.AVm7など)に置き換えられます。

これで、パイプ(またはパイプコマンド)を次のように変更できます。

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

フィルタリング後、次のように一時ファイルを元のファイルに移動できます。

mv "$tempfile" "$filepath"

これにより一時ファイルが削除され、フィルタ処理されたソースファイルはそのまま残ります。ただし、時には必要なく、まだ削除されていない一時ファイルがたくさん生成される可能性があるため、スクリプトの終了後に一時ファイルがない場合は、すべての一時ファイルを削除してディレクトリをクリーンアップすることをお勧めします。これ以上必要です。これに対するルーチンは次のように書くことができます。

remove_temp_files() {
    rm `find . -name "tempFile.????"`
}

remove_temp_files次に、上記の形式で生成されたすべての一時ファイルを削除するスクリプトの最後のルーチンを呼び出します。

答え3

使用ここのドキュメントそしてコマンドの置き換えこの場合、標準的なアプローチは次のとおりです。

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

他の質問については、以前の多くの質問に説明があります。

関連情報