IO重定向和head命令


9

.hgignore今天,我试图从Cygwin bash shell 快速编辑文件,并且添加了一行错误。我不确定这是否是最好的方法,但是我很快想到了使用head -1 .hgignore删除有问题的行(我以前在文件中只有一行)。果然,执行时,它会将第一行作为唯一输出。

但是,当我尝试重定向输出并使用重写文件时head -1 .hgignore > .hgignore,该文件为空。为什么会这样?如果我尝试附加head -1 .hgignore >> .hgignore,它会正确附加,但这显然不是理想的结果。为什么在这种情况下无法进行截断的重定向?


Answers:


10

当外壳程序获得如下命令行时:command > file.out外壳程序本身打开(并可能创建)名为的文件file.out。Shell将文件描述符0设置为它从打开处获得的文件文件描述符。这就是I / O重定向的工作方式:每个进程都知道文件描述符0、1和2。

困难的部分是如何打开file.out。在大多数情况下,您想file.out在偏移量0(即被截断)处打开以进行写入操作,这就是Shell为您完成的工作。它截断了.hgignore,将其打开以进行写入,将filedescriptor设置为0,然后执行head。即时文件破坏。

在bash shell中,执行a set noclobber更改此行为。


啊哈,我明白了。我确实认为Shell在运行命令之前会截断文件,但是我不知道为什么。感谢您的解释!
voithos 2011年

10

我认为Bruce 用Shell管道回答了这里发生的事情

我最喜欢的小实用程序之一是moreutilssponge命令。它通过在打开目标输出文件并写入数据之前“吸收”所有可用输入来解决此问题。它使您可以按照预期的方式编写管道:

$ head -1 .hgignore | sponge .hgignore

穷人的解决方案是将输出通过管道传输到一个临时文件,然后在完成pipline之后(例如,您运行的下一个命令)是将临时文件移回原始文件位置。

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}

几年后,看着这个,我想到一个念头:我们不能做head -1 .hgignore | tee .hgignore吗?tee是在coreutils中的,并且作为一种特权/副作用,这也写给STDOUT
voithos 2014年

就我所知,@ voithos tee与其他所有实例一样,在实例化时将打开并截断正在写入的文件,因此在用写入方式截断文件内容之前,它无法解决此处的竞争条件的主要问题。
Caleb 2014年

您实际上提出了我不知道的一点-即,管道命令立即启动,而不是顺序启动。准确吗?但是,我确实进行了测试,tee 似乎可以完成所需的操作。8.13我的机器上有版本。
voithos

1
pipline中的@voithos Yes命令和涉及的所有输入/输出通道以相反的顺序启动,因此,当第一个开始提供数据时,管道已准备好接收数据。我怀疑您的测试存在缺陷,因为您可能使用了很少的数据块,并且在需要之前将整个内容缓存在读取缓冲区中。该tee程序将截断您的文件,它没有设置为对文件进行双缓冲。
Caleb 2014年

3

head -n 1 file > file

filehead开始之前被截断,但是如果您编写它:

head -n 1 file 1<> file

它不是以file读写模式打开的。但是,head完成写入后,它不会截断文件,因此,上面的行是空操作(head只会将第一行重写为自身,而其他行则保持不变)。

但是,在head返回之后并且fd仍处于打开状态时,您可以调用执行的另一个命令truncate

例如:

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

这里的问题是,truncate上面,head才刚刚第一行后移动光标FD 1里面的文件。它确实重写了我们不需要的第一行,但这不是有害的。

使用POSIX头,我们实际上可以无需重写第一行就可以摆脱困境:

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

在这里,我们利用了head将光标位置移动到其标准输入中的事实。虽然head通常会大块读取其输入以提高性能,但是POSIX会要求它(如果可能)在seek超出第一行后立即返回。但是请注意,并非所有的实现都执行此操作。

或者,read在这种情况下,可以改用shell的命令:

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file

1
史蒂芬(Stephane),您知道一个标准或coreutils命令可以截断STDIN类似于您在perl上面使用的命令所截断的内容吗
iruvar,2015年

2
@ 1_CR,不。dd可以在文件中的任意绝对偏移处截断。所以,你可以确定字节与第二线并截断的偏移,从那里dd bs=1 seek="$offset" of=file
斯特凡Chazelas

1

真正的男人的解决方案是

ed .hgignore
$d
wq

或单线

printf '%s\n' '$d' 'wq' | ed .hgignore

或使用GNU sed:

sed -i '$d' .hgignore

(不,我在开玩笑。我会使用交互式编辑器。vi .hgignore GddZZ


我想知道,使用:wqover 有什么优势ZZ吗?
voithos 2011年

另外,:x这就是我的手指自动执行的操作
glenn jackman 2011年

ZQ:q!
glenn jackman 2011年

ZZ和:x仅在有东西要写时才写...:w总是将文件同步到磁盘,无论是否需要它。我使用:xa是因为我使用制表符。
xenoterracide 2012年

1

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

ex -sc '2,d|x' .hgignore
  1. 2, 选择第2行直到结束

  2. d 删除

  3. x 保存并关闭


0

对于就地文件编辑,您还可以使用打开文件处理技巧,如JürgenHötzel在sed's / c / d /'myFile到myFile的重定向输出中所示。

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed

2
而在rm .hgignore您的电源故障之后,就可以节省数小时的辛苦工作。好的,这并不重要.hgignore,但是为什么您仍然要做一些复杂的事情?因此,我的反对意见是:技术上正确,但非常糟糕的主意。
吉尔(Gilles)“所以,别再邪恶了”

@Gilles,也许不是一个好主意,但这就是perl -i(用于就地编辑)的功能,如果它的某些实现sed -i也做到这一点,我不会感到惊讶(尽管最新版本的GNU sed似乎没有)。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.