IT-Swarm.Net

如何在命令中使用文件并将输出重定向到同一文件而不截断它?

基本上我想从文件中取输入文本,从该文件中删除一行,然后将输出发送回同一文件。沿着这些方向的东西,如果这使它更清楚。

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

但是,当我这样做时,我最终得到一个空白文件。有什么想法吗?

74
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。

70
c00kiemon5ter

使用 sponge 进行此类任务。它是moreutils的一部分。

试试这个命令:

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

请改用sed:

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

试试这个简单的

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

这次你的文件不会是空白的:)你的输出也会打印到你的终端。

8
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来实现相同的功能(如其他答案所示),但是就地-i)是非标准的FreeBSD扩展(可能在Unix/Linux之间有所不同),基本上它是 s tream ed itor,而不是文件编辑器。请参阅: Ex模式是否有实际用途?

6
kenorb

一个班轮替代方案 - 将文件的内容设置为变量:

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

你可以使用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来解决这个问题,所以这是一个黑客攻击。

在你的例子中:

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时间来解析输入文件
  • 最后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
1

我通常使用 tee program来执行此操作:

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

它自己创建和删除临时文件。

1
Carlos Fanelli

由于这个问题是搜索引擎的最佳结果,这里是 https://serverfault.com/a/547331 使用子shell而不是sponge(通常不是Vanilla安装的一部分)的单行像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

而在当前的Shell中调用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 Simhakos 借用了这个答案。

1
Zack Morris

以下将完成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
Mike Nakis