Linux:ファイルを入力と出力として同時に使用する方法


55

bashで次を実行しました。

uniq .bash_history > .bash_history

履歴ファイルは完全に空になりました。

書き込む前にファイル全体を読み取る方法が必要だと思います。どうやって?

PS:一時ファイルの使用を考えていましたが、よりエレガントなソリューションを探しています。


ファイルが右から左に開かれるためです。stackoverflow.com/questions/146435/…
WheresAlice

同じディレクトリの新しいファイルに出力を書き込み、古いファイルの上に名前を変更する必要があります。他の方法では、途中で中断されるとデータが失われる危険があります。一部のツールは、このステップを非表示にする場合があります。
カスペルド

または、bashHISTCONTROLを設定してigno​​reupsを含める場合、履歴に連続した重複を入れません。マンページを参照してください。
-dave_thompson_085

回答:


49

moreutilsspongeからの使用をお勧めします。マンページから:

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

これを問題に適用するには、次を試してください。

uniq .bash_history | sponge .bash_history

6
それは猫のようなものですが、吸う能力を持つ:D
MilliaLover

77

私は単純で、スポンジを使用しない別の答えを投稿したかっただけです(なぜなら、それはしばしば軽量環境に含まれていないからです)。

echo "$(uniq .bash_history)" > .bash_history

望ましい結果が得られるはずです。サブシェルは、.bash_historyが書き込み用に開かれる前に実行されます。Phil Pの答えで説明したように、.bash_historyが元のコマンドで読み取られるまでに、 '>'演算子によって既に切り捨てられています。


15
私は通常、すでに有効で受け入れられた答えを持っている古代の質問をd​​rする答えのファンではありません-しかし、これはエレガントで、よく書かれており、その必要性(軽量環境)を強く主張しています。私にとっては、既存の回答セットに何かを本当に追加します。SF、Hartへようこそ(1か月間ここにいましたが、これが最初の実質的な投稿だと思います)。このようなあなたからのより多くの答えを読みたいと思います!
MadHatter

4
これが最善の解決策です。$()エスケープの問題があるため、バックティックの代わりにサブシェルを使用する必要がありました。
CMCDragonkai

3
このソリューションが大きなファイル、たとえば20 GBまたは50 GBに拡張できるかどうか疑問に思っています。
アミットナイドゥ

1
これは本当に受け入れられる答えでなければなりません。
maxywb

1
echo "$(fmt -p '# ' -w 50 readme.txt)" > readme.txt今日はこの回答を使用しました。エレガントな解決策を長い間探し求めていました。@Hart Simha、ありがとう!
shredalert

12

問題は、コマンドを実行する前にシェルがコマンドパイプラインを設定していることです。「入力と出力」の問題ではなく、uniqが実行される前にファイルのコンテンツがすでに削除されているということです。次のようになります:

  1. シェルは>出力ファイルを書き込み用に開き、切り捨てます
  2. シェルは、その出力にファイル記述子1(stdout用)を使用するように設定します
  3. シェルは、おそらくexeclp( "uniq"、 "uniq"、 ".bash_history"、NULL)のようなuniqを実行します
  4. uniqが実行され、.bash_historyが開き、そこで何も見つかりません

インプレース編集や一時ファイルの使用など、さまざまなソリューションがありますが、重要なのは、問題、実際に何が問題になっているのか、そしてその理由を理解することです。


9

を使用せずにこれを行う別のトリックspongeは、次のコマンドです。

{ rm .bash_history && uniq > .bash_history; } < .bash_history

これは、backreference.orgの優れた記事「ファイルのインプレース編集」で説明されているチートの1つです。

基本的に読み取り用にファイルを開き、それを「削除」します。ただし、実際には削除されていません。それを指すオープンファイル記述子があり、それが開いたままである限り、ファイルはまだ存在しています。次に、同じ名前の新しいファイルを作成し、一意の行を書き込みます。

このソリューションの欠点:uniq何らかの理由で失敗した場合、履歴は失われます。



3

このsedスクリプトは、隣接する重複を削除します。では-iオプション、それはその場で修正を行います。それはsed infoファイルからです:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history

sedはまだ一時ファイルを使用し、straceイラスト付きの回答を追加しました(実際には重要ではありません):
カイルブラント

3
@Kyle:本当ですが、「見えない、気にしない」。個人的にprocess input > tmp && mv tmp inputsed、一時ファイルを避けるためにトリックを使用するよりもはるかにシンプルで読みやすいので、明示的な一時ファイルを使用します。失敗した場合、元のファイルは上書きされません(sed -i正常に失敗したかどうかはわかりません-私はしかしそれだと思います)。それに、temp-to-temp-fileに出力する方法でできることはたくさんありますが、このsedスクリプトよりも複雑なものがなければ、インプレースで行うことはできません。あなたはこれをすべて知っていることを知っていますが、それは見物人の利益になるかもしれません。
デニスウィリアムソン

3

興味深いヒントとして、sedは一時ファイルも使用します(これは単にあなたのためにそれを行います)。

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

説明:
一時ファイル./sedPmPv9zはfd 4になり、fooファイルはfd 3になります。読み取り操作はfd 3で行われ、書き込みはfd 4(一時ファイル)で行われます。fooファイルは、名前変更呼び出しで一時ファイルで上書きされます。



0

問題のコマンドがたまたまその場での編集をサポートしていない限り、一時ファイルはほとんどそれです(uniqしません-いくつかsedはそうします(sed -i))。


0

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

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

  2. ! コマンドを実行する

  3. x 保存して閉じます


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.