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

mik*_*ike 84 io bash redirect

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

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name
Run Code Online (Sandbox Code Playgroud)

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

Lyn*_*nch 80

海绵做这种任务.它是moreutils的一部分.

试试这个命令:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你的回答.作为一个可能有用的补充,如果你在Mac上使用自制软件,可以使用`brew install moreutils`. (3认同)
  • 该死的!感谢您向我介绍moreutils =)一些不错的程序! (3认同)
  • 或者在基于Debian的系统上"sudo apt-get install moreutils". (2认同)
  • 请注意,“海绵”是破坏性的,因此,如果命令中有错误,则可以清除输入文件(就像我第一次尝试海绵一样)。如果您尝试迭代使命令起作用,请确保您的命令起作用,并且/或者输入文件受版本控制。 (2认同)

c00*_*ter 76

你不能这样做,因为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}
Run Code Online (Sandbox Code Playgroud)

像这样,考虑使用mktemp创建tmp文件,但请注意它不是POSIX.

  • 你不能这样做的原因:bash首先处理重定向,然后执行命令.所以当grep查看file_name时,它已经是空的. (43认同)
  • 是的,但值得注意的是,在这种情况下,```重定向将打开文件并截断​​它****之前shell启动`grep`. (2认同)

Man*_*y D 17

请改用sed:

sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
Run Code Online (Sandbox Code Playgroud)

  • 在\*BSD(因此也是OSX)上,您可以说`-i ''`,因此扩展名不是严格强制性的,但`-i` 选项确实需要*some* 参数。 (4认同)
  • iirc `-i` 是 GNU 唯一的扩展,请注意。 (2认同)

小智 9

试试这个简单的

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
Run Code Online (Sandbox Code Playgroud)

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

  • 这也会清除此处的文件内容。这是由于 GNU/BSD 的差异吗?我在 macOS 上... (6认同)
  • 不保证,与 /sf/answers/3582166521/ 相同 (2认同)

ken*_*orb 8

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

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

ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name
Run Code Online (Sandbox Code Playgroud)

哪里:

  • '+cmd'/ -c-运行任何Ex / Vim命令
  • g/pattern/d-使用全局help :g)删除与模式匹配的行
  • -s-静音模式(man ex
  • -c wq-执行:write:quit命令

您可以使用sed来实现相同的(在其他的答案已经显示),但就地-i)是非标准的FreeBSD扩展(可以在Unix / Linux之间的工作方式不同),基本上它是一个小号 tream itor,而不是一个文件编辑器。请参阅:防爆模式有实际用途吗?


pis*_*che 7

这是很有可能的,您只需确保在写入输出时,将其写入不同的文件即可。这可以通过在打开文件描述符之后但在写入文件之前删除该文件来完成:

exec 3<file ; rm file; COMMAND <&3 >file ;  exec 3>&-
Run Code Online (Sandbox Code Playgroud)

或者逐行,以便更好地理解它:

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
Run Code Online (Sandbox Code Playgroud)

这仍然是一件有风险的事情,因为如果 COMMAND 无法正常运行,您将丢失文件内容。如果 COMMAND 返回非零退出代码,可以通过恢复文件来缓解这种情况:

exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-
Run Code Online (Sandbox Code Playgroud)

我们还可以定义一个 shell 函数以使其更易于使用:

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

例子 :

$ echo aaa > test
$ replace test tr a b
$ cat test
bbb
Run Code Online (Sandbox Code Playgroud)

另请注意,这将保留原始文件的完整副本(直到第三个文件描述符关闭)。如果您使用的是 Linux,并且您正在处理的文件太大,无法在磁盘上放置两次,您可以查看此脚本,该脚本将通过管道将文件逐块传输到指定的命令,同时取消分配已处理的文件块。与往常一样,请阅读使用页面中的警告。


w00*_*00t 6

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

VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
Run Code Online (Sandbox Code Playgroud)


Zac*_*ris 5

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

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

一般情况是:

echo "$(cat file_name)" > file_name
Run Code Online (Sandbox Code Playgroud)

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

  • 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 指出的那样,此答案应用于二进制文件或任何使用不可打印字符的内容。

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

(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

应该打印:

hello
world
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

打印一个空字符串。

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

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

  • 当然,它不适用于大文件。这不可能是一个好的解决方案或一直有效。发生的事情是 bash 首先执行命令,然后加载 `cat` 的标准输出并将它作为第一个参数传递给 `echo`。当然,不可打印的变量将无法正确输出并损坏数据。不要尝试将文件重定向回其自身,这不会很好。 (2认同)