web-dev-qa-db-ja.com

ファイルの読み取りと書き込み:teeコマンド

次のようなコマンドがよく知られています:

cat filename | some_sed_command >filename

出力リダイレクトがコマンドの前に実行されると、ファイルfilenameが消去され、ファイル名が切り捨てられます。

次の方法で問題を解決できます。

cat file | some_sed_command | tee file >/dev/null

しかし、これがどのような場合でも機能するかどうかはわかりません。ファイル(およびsedコマンドの結果)が非常に大きい場合はどうなりますか?オペレーティングシステムは、まだ読み取られていないコンテンツを上書きすることをどのように回避できますか?どのような場合でも動作するスポンジコマンドもあることがわかります。Tシャツよりも「安全」ですか。

9
VeryHardCoder

次の方法で問題を解決できます。

cat file | some_sed_command | tee file >/dev/null

いいえ

fileは切り捨てられますが、cat file | some_sed_command | tee file >/dev/nullfileを切り捨てないという保証はありません。

それはすべて、予想されるものとは対照的に、最初に処理されるコマンドに依存します。 パイプ内のコマンドは左から右に処理されません 。どのコマンドが最初に選択されるかについての保証はありません。したがって、ランダムに選択されたと考えて、neverシェルが問題のあるコマンドを選択しないことに頼ることもできます。

3つのコマンド間で最初に問題のコマンドが選択される可能性は、2つのコマンド間で最初に問題のコマンドが選択される可能性よりも低いため、fileが切り捨てられる可能性は低くなりますが、それでも起こります

script.sh

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

nevercat file | some_sed_command | tee file >/dev/nullのようなものを使用します。 Oliが提案したようにspongeを使用します。

代替として、より厳しい環境および/または比較的小さなファイルの場合、here文字列とコマンド置換を使用して、コマンドを実行する前にファイルを読み取ることができます:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
9
kos

特にsedには、その-iインプレース引数を使用できます。開いたファイルに保存するだけです。例:

sed -i 's/ /-/g' filename

あなたがsed以上をしていると仮定して、より強力な何かをしたい場合は、spongemoreutilsパッケージから)ですべてをバッファリングすることができます。 teeに似ていますが、機能はほとんどありません。ただし、基本的な使用方法については、ドロップインの代わりになります。

cat file | some_sed_command | sponge file >/dev/null

それは安全ですか?絶対に。おそらく制限がありますので、巨大な(sedでインプレース編集できない)場合は、2番目のファイルを編集してから、そのファイルをmvして元のファイル名に戻したい場合があります。これはアトミックである必要があります(したがって、これらのファイルに依存するものは、常にアクセスする必要がある場合に壊れません)。

9
Oli

ああ、しかしspongeが唯一のオプションではありません。これを適切に機能させるためにmoreutilsを取得する必要はありません。次の2つの要件を満たす限り、どのメカニズムも機能します。

  1. 出力ファイルの名前をパラメーターとして受け入れます。
  2. すべての入力が処理されると、出力ファイルが作成されます。

OPが言及しているよく知られている問題は、パイプラインでコマンドの実行を開始する前にパイプが機能するために必要なすべてのファイルをシェルが作成することであるため、実際に切り捨てられるのはシェルですコマンドのいずれかが実行を開始する前に、出力ファイル(残念ながら入力ファイルでもあります)。

teeコマンドは、最初の要件を満たしていても機能しません。2番目の要件を満たさないためです。開始直後に常に出力ファイルを作成するため、基本的にパイプを作成するのと同じくらい悪いです。出力ファイルに。 (実際には、出力ファイルが切り捨てられる前に非決定的なランダム遅延が発生するため、実際にはそうではないが、実際には機能しないと思われるかもしれません。)

したがって、この問題を解決するために必要なのは、出力を生成する前にすべての入力をバッファリングし、出力ファイル名をパラメータとして受け入れることができるコマンドであり、出力をパイプする必要はありません出力ファイル。そのようなコマンドの1つはshufです。したがって、以下はspongeと同じことを実現します。

    shuf --output=file --random-source=/dev/zero 

--random-source=/dev/zero部分はshufをだましてシャッフルを一切行わずにその処理を実行するため、入力を変更せずにバッファリングします。

0
Mike Nakis

ExモードでVimを使用できます。

ex -sc '%!some_sed_command' -cx filename
  1. %すべての行を選択

  2. !コマンドを実行

  3. x保存して終了

0
Steven Penny