我只是在bash中运行以下命令:

uniq .bash_history > .bash_history


,我的历史记录文件最终完全为空。

我想我需要一种方法在写入文件之前先读取整个文件。
如何完成?

PS:我显然想过要使用临时文件,但我正在寻找更优雅的解决方案。

评论

这是因为文件是从右到左打开的。另请参见stackoverflow.com/questions/146435/…

您必须将输出写入同一目录中的新文件,然后将其重命名为旧文件。如果其他方法中途中断,则可能会丢失数据。有些工具可能会让您看不到这一步。

或者,如果将HISTCONTROL设置为包含忽略的内容,bash不会在其历史记录中放置连续的重复对象;请参阅联机帮助页。

请考虑更改此答案。 serverfault.com/a/557566/130392

#1 楼

我建议使用moreutils中的sponge。在联机帮助页上:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.


要将其应用于您的问题,请尝试:

uniq .bash_history | sponge .bash_history


评论


它就像猫,但具有吸吮能力:D

–MilliaLover
10年4月28日在0:11

#2 楼

echo "$(uniq .bash_history)" > .bash_history

应具有所需的结果。在打开.bash_history进行写入之前,将执行该子shell。如Phil P的答案所述,到原始命令中读取.bash_history时,它已经被>运算符截断了。

评论


我今天用这个答案做echo“ $(fmt -p'#'-w 50 readme.txt)”> readme.txt。寻找了很长时间寻找一个优雅的解决方案。非常感谢,@ Hart Simha!

– shredalert
19年6月6日在12:05

如果回显中的命令引发错误,则将清除文件

–鲍里斯(Boris)
20年11月3日,19:06

#3 楼

问题是您的外壳在运行命令之前正在建立命令管道。这与“输入和输出”无关,因为文件的内容在uniq运行之前就已经消失了。它的内容类似于:


shell打开>输出文件进行写入,将其截断
shell设置为使用文件描述符1(用于stdout)用于输出结果
Shell执行uniq,也许类似于execlp(“ uniq”,“ uniq”,“ .bash_history”,NULL)
uniq运行,打开.bash_history,在此什么也没找到

有各种各样的解决方案,包括其他人提到的就地编辑和临时文件使用,但是关键是要了解问题,实际出了什么问题以及原因。

#4 楼

不使用sponge的另一种方法是以下命令:

{ rm .bash_history && uniq > .bash_history; } < .bash_history


这是出色的文章“就地”编辑中描述的作弊技巧之一backreference.org上的文件。

它基本上是打开文件进行读取,然后“删除”它。但是,它并没有真正被删除:有一个指向它的打开的文件描述符,只要保持打开状态,文件就仍然存在。然后,它将创建一个具有相同名称的新文件,并在其中写入唯一的行。

此解决方案的缺点:如果uniq由于某种原因失败,则您的历史记录将消失。

#5 楼

使用moreutils提供的海绵

uniq .bash_history | sponge .bash_history


#6 楼

作为一个有趣的花絮,sed也会使用一个临时文件(这正是为您做的):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0


描述:
临时文件./sedPmPv9z变成fd 4 ,并且foo文件变成fd3。读取操作在fd 3上,写入在fd 4(临时文件)上。然后在重命名调用中用临时文件覆盖foo文件。

#7 楼

sed脚本删除了相邻的重复项。使用-i选项,它就地进行修改。来自sed info文件:

sed -i 'h;:b;$b;N;/^\(.*\)\n$/ {g;bb};$b;P;D' .bash_history


评论


sed仍然使用temp文件,并添加了带有strace插图的答案(并不是真的很重要):-)

–凯尔·勃兰特(Kyle Brandt)
2010-4-25的1:15

@Kyle:的确如此,但是“视而不见,心不在“”。就个人而言,我会使用显式的临时文件,因为类似于sed欺骗,以避免输入临时文件,比起sed骗术,过程输入> tmp && mv tmp输入之类的东西更简单易读,并且在失败时也不会覆盖原始文件(我不会不知道sed -i是否正常失败-但我认为会)。此外,使用output-to-temp-file方法可以做很多事情,如果没有比这个sed脚本更复杂的事情,就无法就地完成。我知道您知道所有这一切,但这可能会使围观者受益。

–丹尼斯·威廉姆森
2010-4-25的2:54

#8 楼

另一个解决方案:

uniq file 1<> líneas.txt


#9 楼

临时文件就差不多了,除非有问题的命令碰巧支持就地编辑(uniq不支持-一些sed可以(sed -i))。

#10 楼

您可以在Ex模式下使用Vim:

 ex -sc '%!uniq' -cx .bash_history
 



%选择所有行
!运行命令
x保存并关闭


#11 楼

您也可以使用tee,并使用uniq输出作为输入:

uniq .bash_history | tee .bash_history


评论


不,您不能这样做askubuntu.com/a/752451

–史蒂芬·潘妮(Steven Penny)
16年4月23日在19:59