基本上,我想将文件中的文本作为输入文本,从该文件中删除一行,然后将输出发送回相同的文件。如果可以更清楚地理解这些内容。

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


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

评论

也请参见以下内容:如何使在同一管道中读取和写入同一文件始终“失败”?在Unix和Linux SO上。

#1 楼

您不能这样做,因为bash首先处理重定向,然后执行命令。因此,当grep查看file_name时,它已经为空。不过,您可以使用一个临时文件。
像这样,考虑使用mktemp创建tmpfile,但请注意,它不是POSIX。

评论


之所以不能这样做,是因为bash首先处理重定向,然后执行命令。因此,当grep查看file_name时,它已经为空。

–格伦·杰克曼
2011年7月14日在17:27

@glennjackman:通过“处理重定向,您的意思是在>的情况下打开文件并清除它,而在>>的情况下只打开文件”?

–拉兹万
2015年9月11日14:58在

是的,但是请注意,在这种情况下,>重定向将在外壳启动grep之前打开文件并截断​​它。

–格伦·杰克曼
2015年9月11日在15:48

如果您不想使用临时文件,请参阅我的回答,但请不要对此评论打分。

–扎克·莫里斯(Zack Morris)
18-09-18在20:10

取而代之的是,应该接受使用海绵命令的答案。

–vlz
1月31日在16:37



#2 楼

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

请尝试以下命令:

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


评论


感谢你的回答。作为一项可能有用的补充,如果您在Mac上使用自制软件,则可以使用brew install moreutils。

–安东尼·帕诺佐(Anthony Panozzo)
2013年2月6日在2:12



或者在基于Debian的系统上sudo apt-get install moreutils。

–乔纳
14年8月15日在16:45

该死的!感谢您向我介绍moreutils =)一些不错的程序!

– netigger
2015年5月25日11:00

非常感谢,moreutils的救助!海绵像老板!

– aqquadro
16-10-20在9:30

请注意,“海绵”是破坏性的,因此,如果命令中有错误,则可以清除输入文件(就像我第一次尝试海绵一样)。如果您尝试迭代使命令起作用,请确保您的命令起作用,并且/或者输入文件受版本控制。

–user107172
16 Dec 27 '18:13



#3 楼

请改用sed:

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


评论


iirc -i是GNU唯一的扩展,只是注意。

–c00kiemon5ter
2011年7月14日在16:44

在* BSD(因此还有OSX)上,您可以说-i”,因此扩展名不是严格必需的,但是-i选项确实需要一些参数。

–tripleee
17年11月9日在10:01



#4 楼

试试这个简单的方法

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


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

评论


我喜欢这个解决方案!而且,如果您不希望在终端上打印它,您仍然可以将输出重定向到/ dev / null或类似的地方。

–冻结
16年7月18日在11:17

这也将清除文件内容。那是由于GNU / BSD的差异吗?我在macOS上...

– ssc
18年2月6日在11:45

不保证,与stackoverflow.com/a/51173807/97439相同

–维克
11月20日19:56

#5 楼

您不能对同一文件使用重定向运算符(>>>),因为它具有更高的优先级,并且会在调用命令之前创建/截断文件。为避免这种情况,您应该使用适当的工具,例如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)是非标准的FreeBSD扩展(在Unix / Linux之间可能会有所不同),基本上它是一个流编辑器,而不是文件编辑器。请参阅:防爆模式有实际用途吗?

#6 楼

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

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


#7 楼

由于此问题是搜索引擎中的最高结果,因此这是一个基于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之类的shell功能),因此我们应该在输出后附加诸如x之类的后缀字符,并通过诸如${v%x}之类的临时变量的参数扩展在外部将其删除。
使用临时变量$v会mp足任何现有变量的值$v在当前的shell环境中,因此我们应该将整个表达式嵌套在括号中以保留以前的值。
另一个bug /功能像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)


从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


而在当前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 Simha和kos借用了这个答案。

评论


当然,它不适用于大文件。这不可能是一个好的解决方案,也不能一直工作。发生的情况是bash首先执行命令,然后加载cat的stdout并将其作为echo的第一个参数。当然,不可打印的变量将无法正确输出并破坏数据。不要尝试将文件重定向回自身,这根本不是一件好事。

–林奇
18-09-19在4:12



#8 楼

还有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


#9 楼

您可以使用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时间来解析输入文件
最后cat > file_name写入输出


#10 楼

您可以在POSIX Awk中使用slurp:

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


示例

评论


也许应该指出,“ slurp”的意思是“将整个文件读入内存”。如果输入文件很大,也许您想避免这种情况。

–tripleee
17年11月9日在10:02

#11 楼

这是很有可能的,您只需要确保在编写输出时就将其写入另一个文件即可。这可以通过在打开文件描述符之后但在写入文件之前删除文件来完成:
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>&-

我们还可以定义一个shell函数以使其更易于使用:
# Usage: replace FILE COMMAND
replace() { exec 3< ; rm ; ${@:2} <&3 > || cat <&3 > ; exec 3>&- }

示例:
$ echo aaa > test
$ replace test tr a b
$ cat test
bbb

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

#12 楼

试试这个

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

cat testfile
AAA
BBB
CCC

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


评论


简短的解释甚至评论可能会有所帮助。

–丰富
18年8月29日在18:18

我认为,之所以有效,是因为字符串外推在重定向运算符之前执行,但我不知道

–ВикторПупкин
18年8月30日在11:48

#13 楼

以下内容将实现与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:
#    : the file.
#    : the command. (With ... being its arguments.)
# See https://stackoverflow.com/a/55655338/773113

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


#14 楼

我通常使用tee程序执行此操作:

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


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

评论


抱歉,不能保证tee正常工作。参见askubuntu.com/a/752451/335781。

–studgeek
18/12/13在1:46