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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6696842/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:45:31  来源:igfitidea点击:

How can I use a file in a command and redirect output to the same file without truncating it?

bashredirectio

提问by mike

Basically I want to take as input text from a file, remove a line from that file, and send the output back to the same file. Something along these lines if that makes it any clearer.

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

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

however, when I do this I end up with a blank file. Any thoughts?

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

采纳答案by c00kiemon5ter

You cannot do that because bash processes the redirections first, then executes the command. So by the time grep looks at file_name, it is already empty. You can use a temporary file though.

你不能这样做,因为 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}

like that, consider using mktempto create the tmpfilebut note that it's not POSIX.

像那样,考虑使用mktemp来创建tmpfile但请注意它不是 POSIX。

回答by Lynch

Use spongefor this kind of tasks. Its part of moreutils.

海绵用于此类任务。它是moreutils 的一部分。

Try this command:

试试这个命令:

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

回答by Manny D

Use sed instead:

使用 sed 代替:

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

回答by sailesh ramanam

try this simple one

试试这个简单的

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

Your file will not be blank this time :) and your output is also printed to your terminal.

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

回答by kenorb

You can't use redirection operator (>or >>) to the same file, because it has a higher precedence and it will create/truncate the file before the command is even invoked. To avoid that, you should use appropriate tools such as tee, sponge, sed -ior any other tool which can write results to the file (e.g. sort file -o file).

您不能对同一个文件使用重定向运算符 (>>>),因为它具有更高的优先级,并且会在调用命令之前创建/截断文件。为了避免这种情况,你应该使用合适的工具,例如teespongesed -i或任何其他工具,它可以将结果写到文件(例如sort file -o file)。

Basically redirecting input to the same original file doesn't make sense and you should use appropriate in-place editors for that, for example Ex editor (part of Vim):

基本上将输入重定向到相同的原始文件没有意义,您应该为此使用适当的就地编辑器,例如 Ex 编辑器(Vim 的一部分):

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

where:

在哪里:

  • '+cmd'/-c- run any Ex/Vim command
  • g/pattern/d- remove lines matching a pattern using global(help :g)
  • -s- silent mode (man ex)
  • -c wq- execute :writeand :quitcommands
  • '+cmd'/ -c- 运行任何 Ex/Vim 命令
  • g/pattern/d- 使用global( help :g)删除匹配模式的行
  • -s- 静音模式 ( man ex)
  • -c wq- 执行:write:quit命令


You may use sedto achieve the same (as already shown in other answers), however in-place(-i) is non-standard FreeBSD extension (may work differently between Unix/Linux) and basically it's a stream editor, not a file editor. See: Does Ex mode have any practical use?

您可能会使用它sed来实现相同的(如其他答案中所示),但是就地( -i) 是非标准的 FreeBSD 扩展(在 Unix/Linux 之间的工作方式可能不同)并且基本上它编辑器,而不是文件编辑器. 参见:Ex 模式有什么实际用途吗?

回答by w00t

One liner alternative - set the content of the file as variable:

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

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

回答by Zack Morris

Since this question is the top result in search engines, here's a one-liner based on https://serverfault.com/a/547331that uses a subshell instead of sponge(which often isn't part of a vanilla install like OS X):

由于这个问题是搜索引擎中的最高结果,这里有一个基于https://serverfault.com/a/547331的单行,它使用子外壳而不是sponge(通常不是像 OS X 这样的香草安装的一部分) :

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

The general case is:

一般情况是:

echo "$(cat file_name)" > file_name

Edit, the above solution has some caveats:

编辑,上述解决方案有一些警告:

  • printf '%s' <string>should be used instead of echo <string>so that files containing -ndon't cause undesired behavior.
  • Command substitution strips trailing newlines (this is a bug/feature of shells like bash) so we should append a postfix character like xto the output and remove it on the outside via parameter expansion of a temporary variablelike ${v%x}.
  • Using a temporary variable $vstomps the value of any existing variable $vin the current shell environment, so we should nest the entire expression in parentheses to preserve the previous value.
  • Another bug/feature of shells like bash is that command substitution strips unprintable characters like nullfrom the output. I verified this by calling dd if=/dev/zero bs=1 count=1 >> file_nameand viewing it in hex with cat file_name | xxd -p. But echo $(cat file_name) | xxd -pis stripped. So this answer should notbe used on binary files or anything using unprintable characters, as Lynch pointed out.
  • printf '%s' <string>应该使用而不是echo <string>这样,包含的文件-n不会导致不良行为。
  • 命令替换去除尾随的换行符(这是 bash 之类的 shell 的错误/功能),因此我们应该x在输出中附加一个后缀字符,并通过临时变量的参数扩展(${v%x}.
  • 使用临时变量会$v破坏$v当前 shell 环境中任何现有变量的值,因此我们应该将整个表达式嵌套在括号中以保留先前的值。
  • 像 bash 这样的 shell 的另一个错误/功能是命令替换会null从输出中删除不可打印的字符。我通过调用dd if=/dev/zero bs=1 count=1 >> file_name并以十六进制查看它来验证这一点cat file_name | xxd -p。但是echo $(cat file_name) | xxd -p被剥光了。因此,正如Lynch 指出的那样,此答案应用于二进制文件或任何使用不可打印字符的内容。

The general solution (albiet slightly slower, more memory intensive and still stripping unprintable characters) is:

一般的解决方案(虽然稍微慢一点,更多的内存密集型,仍然剥离不可打印的字符)是:

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

Test from https://askubuntu.com/a/752451:

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

Should print:

应该打印:

hello
world

Whereas calling cat file_uniquely_named.txt > file_uniquely_named.txtin the current shell:

cat file_uniquely_named.txt > file_uniquely_named.txt在当前 shell 中调用:

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

Prints an empty string.

打印一个空字符串。

I haven't tested this on large files (probably over 2 or 4 GB).

我还没有在大文件(可能超过 2 或 4 GB)上测试过这个。

I have borrowed this answer from Hart Simhaand kos.

我从Hart Simhakos借用了这个答案。

回答by laktak

You can do that using process-substitution.

您可以使用process-substitution来做到这一点。

It's a bit of a hack though as bash opens all pipes asynchronously and we have to work around that using sleepso YMMV.

尽管 bash 异步打开所有管道,但这有点黑客,我们必须使用sleepso YMMV解决这个问题。

In your example:

在你的例子中:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
  • >(sleep 1 && cat > file_name)creates a temporary file that receives the output from grep
  • sleep 1delays for a second to give grep time to parse the input file
  • finally cat > file_namewrites the output
  • >(sleep 1 && cat > file_name)创建一个接收 grep 输出的临时文件
  • sleep 1延迟一秒让 grep 有时间解析输入文件
  • 最后cat > file_name写入输出

回答by Steven Penny

You can use slurp with POSIX Awk:

您可以在 POSIX Awk 中使用 slurp:

!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
  q = q ? q RS 
# 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
: ##代码## } END { print q > ARGV[1] }

Example

例子

回答by nerx

There's also ed(as an alternative to sed -i):

还有ed(作为替代sed -i):

##代码##