web-dev-qa-db-ja.com

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

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

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

ただし、これを行うと、空のファイルになります。何かご意見は?

81
mike

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ではないことに注意してください。

75
c00kiemon5ter

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

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

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

代わりにsedを使用します。

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

このシンプルなものを試してください

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

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

9
sailesh ramanam

同じファイルにリダイレクト演算子(>または>>)を使用することはできません。優先順位が高く、コマンドが呼び出される前にファイルを作成/切り捨てるからです。これを避けるには、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- globalhelp :g)を使用してパターンに一致する行を削除します
  • -s-サイレントモード(man ex
  • -c wq-:writeおよび:quitコマンドを実行します

sedを使用して同じことを達成できます(他の回答で既に示したように)が、in-place-i)は非標準のFreeBSDです拡張(Unix/Linux間で異なる動作をする可能性があります)および基本的にはstreameditor、ファイルエディタではありません。参照: Exモードには実用性はありますか?

8
kenorb

ライナーの代替-ファイルの内容を変数として設定します。

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

この質問は検索エンジンのトップの結果であるため、ここに 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

https://askubuntu.com/a/752451 からのテスト:

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do echo "$(cat file_uniquely_named.txt)" > 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または4 GB以上)でテストしていません。

Hart Simha および kos からこの回答を借りました。

3
Zack Morris

SlurpはPOSIX Awkで使用できます。

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

2
Steven Penny

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_が出力を書き込みます
2
laktak

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
2
nerx

これを試して

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

cat testfile
AAA
BBB
CCC

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

これを行うには、通常teeプログラムを使用します。

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

一時ファイルを作成および削除します。

1
Carlos Fanelli

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

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

--random-source=/dev/zero partは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
Mike Nakis