コマンドでファイルを使用し、出力を切り捨てずに同じファイルにリダイレクトするにはどうすればよいですか?


95

基本的に、ファイルから入力テキストを取得し、そのファイルから行を削除し、出力を同じファイルに送り返します。それがより明確になるなら、これらの線に沿った何か。

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name

しかし、これを行うと、空のファイルができてしまいます。何かご意見は?


回答:


84

bashは最初にリダイレクトを処理してからコマンドを実行するため、これを行うことはできません。そのため、grepがfile_nameを調べるときには、すでに空になっています。ただし、一時ファイルを使用できます。

#!/bin/sh
tmpfile=$(mktemp)
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile}
cat ${tmpfile} > file_name
rm -f ${tmpfile}

そのように、を使用mktempしてtmpfileを作成することを検討してください。ただし、それはPOSIXではないことに注意してください。


47
それができない理由:bashは最初にリダイレクトを処理してから、コマンドを実行します。そのため、grepがfile_nameを調べるときには、すでに空になっています。
グレン・ジャックマン、2011

1
@glennjackman:「リダイレクトを処理するということは、>の場合はファイルを開いてそれをクリアし、>>の場合はそれだけを開く」ということですか?
Razvan 2015

2
はい、ただしこの状況では、シェルが起動>する前にリダイレクトによってファイルが開かれ、切り捨てられますgrep
グレン・ジャックマン、2015

1
一時ファイルを使用したくない場合は、私の回答を参照しください。ただし、このコメントには賛成しないでください。
ザックモリス

これの代わりにspongeコマンドを使用し回答を受け入れる必要があります。
vlz

95

この種のタスクにはスポンジを使用します。moreutilsの一部。

このコマンドを試してください:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name

4
答えてくれてありがとう。おそらく役立つ追加として、Macでhomebrewを使用している場合は、を使用できますbrew install moreutils
Anthony Panozzo、2013

2
またはsudo apt-get install moreutils、Debianベースのシステム。
ジョナ

3
くそー!moreutils =)を紹介してくれてありがとうございます。
netigger

どうもありがとう、救助のためのmoreutils!ボスのようなスポンジ!
aqquadro 2016年

3
注意: "スポンジ"は破壊的であるため、コマンドにエラーがある場合は、入力ファイルを消去できます(私が初めてスポンジを試したときのように)。コマンドが機能することを繰り返し確認する場合は、コマンドが機能すること、および/または入力ファイルがバージョン管理されていることを確認してください。
user107172 16

18

代わりにsedを使用してください:

sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name

1
iirc -iは、GNUのみの拡張機能です。
c00kiemon5ter

3
* BSD(ひいてはもOSX)にあなたが言うことができます-i ''拡張子は厳密に必須ではありませんが、そう-iオプションは必要ありませんいくつかの引数を。
tripleee 2017年

13

これを試してみてください

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

今回はファイルが空になりません:)そして、出力は端末にも出力されます。


1
私はこの解決策が好きです!また、ターミナルで出力したくない場合でも、出力を/dev/null同様の場所にリダイレクトできます。
Frozn

4
これにより、ここでもファイルの内容が消去されます。これはGNU / BSDの違いによるものですか?私はmacOSを
使ってい

7

同じファイルにリダイレクト演算子(>または>>)を使用することはできません。これは、優先順位が高く、コマンドが呼び出される前にファイルが作成または切り捨てられるためです。それを避けるには、次のような適切なツールを使用する必要がありteespongesed -iまたはファイル(例えばに結果を書き込むことができ、他のツールをsort file -o file)。

基本的に同じ元のファイルに入力をリダイレクトするのは意味がなく、適切なインプレースエディター、たとえばExエディター(Vimの一部)を使用する必要があります。

ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name

どこ:

  • '+cmd'/ -c-Ex / Vimコマンドを実行する
  • g/pattern/d- グローバルhelp :g)を使用してパターンに一致する行を削除する
  • -s-マナーモード(man ex
  • -c wq-実行:write:quitコマンド

あなたは使用することができますsedが、(既に他の回答のように)同じことを達成するためにインプレース-i(UNIX / Linuxの間で異なる動作をする場合があります)非標準のFreeBSD拡張がある)と基本的には、A社のS tream itor、ないファイルエディタ。参照:Exモードには実用的な用途がありますか?


6

1つの代替ライナー-ファイルのコンテンツを変数として設定します。

VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name

4

この質問は検索エンジンの上位の結果であるため、代わりにサブシェルを使用するhttps://serverfault.com/a/547331に基づくワンライナーは次のとおりですsponge(多くの場合、OS Xのような基本的なインストールの一部ではありません)。 :

echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name

一般的なケースは次のとおりです。

echo "$(cat file_name)" > file_name

編集、上記のソリューションにはいくつかの警告があります:

  • printf '%s' <string>echo <string>含むファイルが-n望ましくない動作を引き起こさないように、代わりに使用する必要があります。
  • 改行を末尾のコマンドの置換ストリップ(これはbashのようなシェルのバグ/機能です)、私たちは以下のように接尾文字を追加する必要がありますのでx、出力へと経由して外部にそれを削除する一時変数のパラメータ展開のように${v%x}
  • 一時変数を使用すると、現在のシェル環境の$v既存の変数の値が踏みにじられる$vため、式全体を括弧でネストして、以前の値を保持する必要があります。
  • bashのようなシェルのもう1つのバグ/機能は、コマンド置換nullが出力からのように印刷できない文字を取り除くことです。これを呼び出しdd if=/dev/zero bs=1 count=1 >> file_name、16進数でを表示して確認しましたcat file_name | xxd -p。しかしecho $(cat file_name) | xxd -p、取り除かれます。だから、この答えはすべきではないとして、印刷不能文字を使用してバイナリファイルか何かで使用することがリンチを指摘しました

一般的な解決策(albietは少し遅く、メモリを大量に消費し、印刷できない文字を取り除きます)は次のとおりです。

(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)

https://askubuntu.com/a/752451からテストします

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do (v=$(cat file_uniquely_named.txt; printf x); printf '%s' ${v%x} > file_uniquely_named.txt); done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

印刷する必要があります:

hello
world

一方cat file_uniquely_named.txt > file_uniquely_named.txt、現在のシェルで呼び出す:

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do cat file_uniquely_named.txt > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

空の文字列を出力します。

私はこれを大きなファイル(おそらく2 GBまたは4 GB以上)でテストしていません。

私はハート・シマコスからこの答えを借りました。


2
もちろん、大きなファイルでは機能しません。これはおそらく良い解決策ではないか、常に機能することはできません。何が起こっているかというと、bashは最初にコマンドを実行し、次にstdoutをロードcatして最初の引数としてに配置しechoます。もちろん、印刷不可能な変数は正しく出力されず、データが破損します。ファイルをそれ自体にリダイレクトしようとしないでください。
リンチ、

1

ed(の代替としてsed -i)もあります:

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq |  ed -s file_name

1

これは、process-substitutionを使用して行うことができます。

bashはすべてのパイプを非同期で開きsleep、YMMV を使用して回避する必要があるため、少しハックです。

あなたの例では:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
  • >(sleep 1 && cat > file_name) grepから出力を受け取る一時ファイルを作成します
  • sleep 1 入力ファイルを解析するためのgrep時間を与えるための1秒の遅延
  • 最後cat > file_nameに出力を書き込みます

1

POSIX Awkでslurpを使用できます。

!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
  q = q ? q RS $0 : $0
}
END {
  print q > ARGV[1]
}


1
「slurp」は「ファイル全体をメモリに読み込む」ことを意味することに注意してください。大きな入力ファイルがある場合は、それを避けたいと思うでしょう。
tripleee 2017年

0

これを試して

echo -e "AAA\nBBB\nCCC" > testfile

cat testfile
AAA
BBB
CCC

echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC

簡単な説明やコメントも役に立ちます。
リッチ

私は、文字列の外挿は、リダイレクト演算子の前に実行されるため、それは仕事、と思いますが、私は正確に知っていない
ВикторПупкин

0

以下spongeは、必要とせずに、同じことを実行しますmoreutils

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

この--random-source=/dev/zero部分はshuf、シャッフルをまったく行わずにその処理を行うようにトリックするため、入力を変更せずにバッファーに入れます。

ただし、パフォーマンス上の理由から、一時ファイルを使用するのが最善です。だから、これは私があなたのために一般化した方法でそれをする私が書いた関数です:

# Pipes a file into a command, and pipes the output of that command
# back into the same file, ensuring that the file is not truncated.
# Parameters:
#    $1: the file.
#    $2: the command. (With $3... being its arguments.)
# See https://stackoverflow.com/a/55655338/773113

function siphon
{
    local tmp=$(mktemp)
    local file="$1"
    shift
    $* < "$file" > "$tmp"
    mv "$tmp" "$file"
}

0

これは非常に可能です。出力を書き込むときに、別のファイルに書き込むことを確認する必要があります。これを行うには、ファイル記述子を開いた後、ファイルに書き込む前にファイルを削除します。

exec 3<file ; rm file; COMMAND <&3 >file ;  exec 3>&-

または行ごとに、よりよく理解するには:

exec 3<file       # open a file descriptor reading 'file'
rm file           # remove file (but fd3 will still point to the removed file)
COMMAND <&3 >file # run command, with the removed file as input
exec 3>&-         # close the file descriptor

COMMANDが正常に実行されない場合、ファイルの内容が失われるため、これは依然として危険なことです。これは、COMMANDがゼロ以外の終了コードを返した場合にファイルを復元することで軽減できます。

exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-

使いやすいシェル関数を定義することもできます:

# Usage: replace FILE COMMAND
replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }

例:

$ echo aaa > test
$ replace test tr a b
$ cat test
bbb

また、これにより、元のファイルの完全なコピーが保持されます(3番目のファイル記述子が閉じられるまで)。Linuxを使用していて、処理中のファイルが大きすぎてディスクに2度収まらない場合は、このスクリプトをチェックして、既に処理されたファイルの割り当てを解除しながら、ファイルを指定されたコマンドにブロックごとにパイプすることができます。ブロック。いつものように、使用法ページの警告を読んでください。


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