sed在整个文件系统上就地删除行?


11

由于尚无法诊断的应用程序错误,我有数百台服务器装有完整的磁盘。有一个文件被重复的行填充了-不是日志文件,而是带有变量定义的用户环境文件(因此,我不能只是删除该文件)。

我编写了一个简单的sed命令来检查错误添加的行并删除它们,然后在文件的本地副本上对其进行测试。它按预期工作。

但是,当我在具有完整磁盘的服务器上尝试使用它时,大约收到以下错误(来自内存,而不是复制和粘贴):

sed: couldn't flush /path/to/file/sed8923ABC: No space left on deviceServerHostname

当然,我知道这里没有空间了。这就是为什么我要删除内容!(sed我正在使用的命令会将4000+行文件减少到大约90行。)

我的sed命令就是sed -i '/myregex/d' /path/to/file/filename

尽管磁盘已满,有没有办法应用此命令?

(它必须是自动化的,因为我需要将它作为快速修复程序应用于几百台服务器。)

(显然,需要诊断应用程序错误,但是在此期间,服务器无法正常工作...。)


更新:通过删除我发现可以删除的其他内容解决了我所面临的情况,但是我仍然希望得到问题的答案,这将对将来和其他人有所帮助。

/tmp不行 它在同一文件系统上。

在释放磁盘空间之前,我进行了测试,发现可以vi打开文件并运行以删除行:g/myregex/d,然后使用保存成功:wq。似乎应该可以自动执行此操作,而无需借助单独的文件系统来保存临时文件....(?)



1
sed -i创建一个临时副本进行操作。我怀疑这样做ed会更好,尽管我不够熟悉,无法提出实际解决方案
Eric Renouf

2
随着ed你运行:printf %s\\n g/myregex/d w q | ed -s infile但请记住,一些实现还使用临时文件一样sed(你可以尝试的busybox版 - AFAIK它不创建一个临时文件)
don_crissti

1
@Wildcard-不可靠w / echo。使用printf。并sed在最后一行添加附加的char,以便避免丢失尾随空格。另外,您的Shell需要能够在单个命令行中处理整个文件。那是您的风险-首先进行测试。bash尤其糟糕(我认为它要占用堆栈空间吗?),并可能随时让您感到不适。sed推荐的两个“ si”至少将使用内核的管道缓冲区在它们之间达到良好的效果,但是方法相当相似。您的命令子对象还将截断files / w成功的sed操作。
mikeserv

1
@Wildcard-尝试sed '/regex/!H;$!d;x' <file|{ read v && cat >file;},如果可行,请阅读其余答案。
mikeserv

Answers:


10

-i选项不会真正覆盖原始文件。它将使用输出创建一个新文件,然后将其重命名为原始文件名。由于您在文件系统上没有空间容纳这个新文件,因此它将失败。

您需要自己在脚本中执行此操作,但是要在其他文件系统上创建新文件。

另外,如果您只是删除与正则表达式匹配的行,则可以使用grep代替sed

grep -v 'myregex' /path/to/filename > /tmp/filename && mv /tmp/filename /path/to/filename

通常,程序几乎不可能使用相同的文件作为输入和输出-一旦开始写入文件,从文件中读取的程序部分将不再看到原始内容。因此,它要么必须首先将原始文件复制到某个位置,要么写入一个新文件,并在完成后对其进行重命名。

如果您不想使用临时文件,则可以尝试将文件内容缓存在内存中:

file=$(< /path/to/filename)
echo "$file" | grep -v 'myregex' > /path/to/filename

1
它是否保留了权限,所有权和时间戳?也许rsync -a --no-owner --no-group --remove-source-files "$backupfile" "$destination"这里开始
Hastur 2015年

@Hastur-您的意思是暗示sed -i确实保留了这些东西吗?
mikeserv

2
@Hastur sed -i不保留任何这些内容。我只是用一个我不拥有的文件尝试了它,但是它位于我拥有的目录中,所以我可以替换该文件。替代品归我所有,而不是原始所有者。
Barmar 2015年

1
@RalphRönnquist可以肯定的是,您需要分两个步骤进行操作:var=$(< FILE); echo "$FILE" | grep '^"' > FILE
Barmar 2015年

1
@Barmar-您不起作用-您甚至都不知道您已成功打开输入。在你可以做最不重要的是v=$(<file)&& printf %s\\n "$v" >file,但你甚至不使用&&。提问者正在谈论在脚本中运行它-自动用文件本身的一部分覆盖文件。您至少应该验证可以成功打开输入和输出。另外,外壳可能会爆炸。
mikeserv

4

就是这样sed。如果与-i(就地编辑)一起使用,sed则会创建一个临时文件,其中包含已处理文件的新内容。完成后sed,用临时文件替换当前工作文件。该实用程序不会就地编辑文件。这就是每个编辑器的行为。

就像您在shell中执行以下任务一样:

sed 'whatever' file >tmp_file
mv tmp_file file

此时sed,尝试通过fflush()系统调用将缓冲的数据刷新到错误消息中提到的文件:

对于输出流,fflush()通过该流的基础写入功能强制为给定输出或更新流写入所有用户空间缓冲的数据。


对于您的问题,我看到了一种安装separte文件系统的解决方案(例如tmpfs,如果您有足够的内存,则为,或者使用外部存储设备),然后将一些文件移到那里,在此处进行处理,然后再移回。


3

自发布此问题以来,我就知道这ex是一个符合POSIX的程序。它几乎与通用符号链接vim,但是无论哪种方式,以下都是(我认为)ex与文件系统有关的关键点(取自POSIX规范):

本节使用术语“ 编辑缓冲区”来描述当前的工作文本。该术语没有暗示具体的实现。所有编辑更改都在编辑缓冲区上执行,并且对其的任何更改都不会影响任何文件,直到编辑器命令写入该文件为止。

“……将影响任何文件……”我相信在文件系统上放一些东西(甚至是临时文件)将被视为“影响任何文件”。也许?*

POSIX规范的ex仔细研究相比,与ex网上常见的脚本使用情况(用vim-特定命令乱七八糟)相比,POSIX规范指出了一些预期的可移植用途“陷阱” 。

  1. +cmd根据POSIX,实施是可选的。
  2. 允许多个-c选项也是可选的。
  3. 全局命令:g“吃掉”所有内容,直到下一个未转义的换行符(因此在为正则表达式找到的每个匹配项之后运行它,而不是在末尾运行一次)。因此,-c 'g/regex/d | x'仅删除一个实例,然后退出文件。

因此,根据我的研究,在整个文件系统上就地编辑文件以删除与特定正则表达式匹配的所有行的POSIX兼容方法是:

ex -sc 'g/myregex/d
x' /path/to/file/filename

如果您有足够的内存将文件加载到缓冲区中,这应该可以工作。

*如果发现任何其他指示,请在评论中提及。


2
但是ex总是写tmpfiles。它的规范是定期将其缓冲区写入磁盘。甚至还有规范命令可用于在磁盘上定位tmp文件缓冲区。
mikeserv '16

@Wildcard感谢您的分享,我在SO的类似帖子上回了链接。我认为ex +g/match/d -scx file也是POSIX兼容的吗?
kenorb '16

根据我对规范的阅读,@ kenorb并不完全,请参阅上面答案中的第一点。POSIX的确切报价是“除未指定的'-'用法外,ex实用程序应符合XBD实用程序语法准则,并且'+' 可以视为选项定界符以及'-'。”
通配符

1
除了诉诸常识之外,我无法证明这一点,但是我相信您从规范中读到的陈述比实际更多。我建议更安全的解释是,对编辑缓冲区的任何更改都不会影响在编辑会话开始之前存在的任何文件,也不会影响用户的命名。另请参阅我对我的回答的评论。
G-Man说'恢复莫妮卡'

@ G-Man,我实际上认为您是对的;我最初的解释可能是一厢情愿的想法。然而,由于在编辑文件vi 的工作在一个完整的文件系统,我认为,在大多数情况下,它会一起工作ex,以及-虽然也许不是一个极大的相文件。 sed -i无论文件大小如何,都无法在完整的文件系统上工作。
2016年

2

使用管道,卢克!

读取文件| 过滤器| 写回

sed 's/PATTERN//' BIGFILE | dd of=BIGFILE conv=notrunc

在这种情况下sed,不会创建一个新文件,而是发送管道输出来dd打开同一文件。当然可以grep在特定情况下使用

grep -v 'PATTERN' BIGFILE | dd of=BIGFILE conv=notrunc

然后截断其余部分。

dd if=/dev/null of=BIGFILE seek=1 bs=BYTES_OF_SED_OUTPUT

1
您是否注意到问题的“完整文件系统”部分?
通配符

1
@Wildcard,是否sed始终使用临时文件? grep反正不会
别人的生活Gleben

这似乎是sponge命令的替代方法。是的,sed-i总是创建lilke“seduyUdmw”万个用权的文件。
巴勃罗A

1

如其他答案中所述, sed -i通过将文件复制到同一目录中的新文件,进行更改,然后将新文件移到原始文件上来进行工作。这就是为什么它不起作用。  ed(原始的行编辑器)的工作方式略有相似,但是,上次我检查时,它/tmp用于草稿文件。如果您的/tmp文件系统与已满的文件系统不在同一文件系统上, ed则可以为您完成此工作。

尝试以下操作(在交互式外壳程序提示符下):

$ ed /路径/到/文件/文件名
P
g / myregex / d
w
q

P(这是一个资本 P)是不是绝对必要的。它打开提示;没有它,您将在黑暗中工作,有些人会感到不安。该wqW¯¯仪式和q UIT。

ed对于神秘的诊断而臭名昭著。如果在任何时候显示的不是提示符(是*),或者是明确确认操作成功的任何东西(特别是如果包含?),请不要写入文件(使用w)。只需退出(q)。如果没有让您失望,请尝试再说q一次。

如果/tmp目录位于已满的文件系统上(或者目录也已满),请尝试在某处找到一些空间。混乱提到安装tmpfs或外部存储设备(例如闪存驱动器);但是,如果你有多个文件系统,并且它们不是全部满了,你可以简单地使用其他现有的一个。混乱的建议是将文件复制到另一个文件系统,在此处进行编辑(使用sed),然后再复制回去。在这一点上,这可能是最简单的解决方案。但是一种替代方法是在具有一些可用空间的文件系统上创建可写目录,设置环境变量TMPDIR以指向该目录,然后运行ed。(公开:我不确定这是否会奏效,但不会造成伤害。)

开始ed工作后,您可以通过

ed 文件名 << EOF
g / myregex / d
w
q
紧急行动

在脚本中。或者 ,如don_crissti所建议。printf '%s\n' 'g/myregex/d' w q | ed -s filename


嗯 是否可以(使用ed或使用ex)完成同一件事,以便使用内存而不是单独的文件系统?那就是我真正想要的(也是我没有接受答案的原因。)
通配符

嗯 这可能比我意识到的要复杂。我在ed很多年前研究了来源。仍然有诸如16位计算机之类的东西,在这些计算机上,进程限于64K(!)地址空间,因此,将整个文件读入内存的编辑器的想法是无用的。从那以后,当然,内存变得越来越大,但是磁盘和文件也越来越大。由于磁盘是如此之大,因此人们不需要处理/tmp空间用尽的情况。我只是快速浏览了的最新版本的源代码ed,它似乎仍然...(续)
G-Man说'Reinstate

(续)……无条件地将“编辑缓冲区”实现为临时文件,而且我找不到任何迹象表明任何版本的ed(或exvi)都提供了将缓冲区保存在内存中的选项。  另一方面, 使用ed和vi进行文本编辑–第11章:文本处理–第2部分:探索Red Hat Linux – Red Hat Linux 9 Professional Secrets – Linux系统表示ed“编辑缓冲区驻留在内存中,……(续) )
G-Man说'Resstate Monica''16

(续)…和Balasubramaniam Srinivasan撰写的UNIX Document Processing and Typesetting关于相同的事情vi(与相同的程序ex)。我相信他们只是在使用草率的,不精确的措词-但是,如果它在Internet上(或印刷版中)使用,则必须正确,对吗?您付钱,然后选择。
G-Man说'恢复莫妮卡'

但是无论如何,我添加了一个新答案。
G-Man说'恢复莫妮卡'

1

如果您可以使字节数达到偏移量,并且行从起点到终点发生,则可以很容易地截断文件。

o=$(sed -ne'/regex/q;p' <file|wc -c)
dd if=/dev/null of=file bs="$o" seek=1

否则,如果您${TMPDIR:-/tmp}在其他文件系统上,则可能是:

{   cut -c2- | sed "$script" >file
} <file <<FILE
$(paste /dev/null -)
FILE

因为(大多数) shell将其此处文档放在删除的临时文件中。只要<<FILE描述符从头到尾都得到维护,并且${TMPDIR:-/tmp}具有所需的空间,这是绝对安全的。

不使用临时文件的Shell使用管道,因此使用这种方式并不安全。这些炮弹是典型的ash衍生物等busyboxdash,BSD sh- ,zshbashksh和Bourne shell的,但是,所有使用临时文件。

显然我去年七月写了一个小shell程序来做这样的事情


如果/tmp不可行,那么只要您可以将文件放入内存中,例如...

sed 'H;$!d;x' <file | { read v &&
sed "$script" >file;}

...作为一般情况,至少会确保sed在尝试截断输入/输出文件之前,第一个进程已完全缓冲该文件。

更具针对性和效率的解决方案可能是:

sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}

...因为它不会打扰您打算删除的缓冲行。

对一般情况的检验:

{   nums=/tmp/nums
    seq 1000000 >$nums
    ls -lh "$nums"
    wc -l  "$nums"
    sed 'H;$!d;x' <$nums | { read script &&  ### read always gets a blank
    sed "$script" >$nums;}
    wc -l  "$nums"
    ls -lh "$nums"
}

-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1000000 /tmp/nums
1000000 /tmp/nums
-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums

我承认我之前没有详细阅读您的答案,因为它以不可行的解决方案(对我而言)开头,涉及字节数(在许多服务器中各不/tmp相同)并且位于同一文件系统上。我喜欢你的双重sed版本。我认为结合使用Barmar和您的答案可能是最好的,例如:(myvar="$(sed '/myregex/d' < file)" && [ -n "$myvar" ] && echo "$myvar" > file ; unset myvar 对于这种情况,我不在乎保留尾随的换行符。)
Wildcard

2
@Wildcard-可能是。但是您不应该像数据库一样使用Shell。该sed| 除非已经缓冲了整个文件并准备开始将其全部写入输出,否则cat上述内容永远不会打开sed输出。如果它尝试缓冲文件并失败- read则不成功,因为读取第一个换行符之前先|管道上找到了EOF ,因此直到它完全从内存中将其写出之前,都不会发生。溢出或类似的任何事情都会失败。整个管道每次都会返回成功或失败。将其存储在var中更具风险。cat >out
mikeserv

@Wildcard-如果我也真的想在变量中使用它,我认为id可以这样做:file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shite因此输出文件和var会同时写入,这将使文件有效或备份有效,这是您想要的唯一原因使事情变得更加复杂。
mikeserv

@mikeserv:我现在正在处理与OP相同的问题,我发现您的解决方案确实有用。但是我不理解read scriptread v在您的答案中的用法。如果您可以详细说明,将不胜感激,谢谢!
sylye

1
@sylye- $scriptsed用于定位文件所需部分的脚本;它的脚本可以为您提供流中所需的最终结果。v只是一个空行的占位符。在bashshell中是没有必要的,因为如果您不指定shell变量,bash它将自动$REPLY代替它使用shell变量,但是POSIXly应该始终这样做。我很高兴您发现它很有用。祝你好运。如果您需要进一步的信息,请发送电子邮件至mikeserv @ gmail。我将在几天后再次拥有一台计算机
mikeserv

0

该答案从该另一个答案该另一个答案中借用了一些想法,但都基于这些想法,从而创建了一个更普遍适用的答案:

num_bytes = $(sed'/ myregex / d' / path / to / file / filename | wc -c)
sed'/ myregex / d' / path / to /文件/文件名 1 <> / path / to /文件/文件名 
dd if = / dev / null of = / path / to /文件/文件名 bs =“ $ num_bytes” = 1

第一行运行sed命令,并将输出写入标准输出(而不是文件);具体来说,是通过管道wc来计算字符。第二行也运行sed与写到标准的输出,这在此情况下被重定向到输入文件中读/写覆盖(没有截断)模式,其被论述输出命令这里。这是一件危险的事情。仅当filter命令从不增加数据(文本)数量时,它才是安全的;也就是说,它每读取n个字节,就写入n个或更少的字节。对于sed '/myregex/d'命令当然是这样;对于它读取的每一行,它都会写入完全相同的行,或者什么也不写。(其他示例:s/foo/fu/s/foo/bar/将是安全的,但s/fu/foo/s/foo/foobar/不会。)

例如:

$ cat filename
It was
a dark and stormy night.
$ sed '/was/d' filename 1<> filename
$ cat filename
a dark and stormy night.
night.

因为这32个字节的数据:

I  t     w  a  s \n  a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

被这25个字符覆盖:

a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

night.\n在结尾保留七个字节。

最后,该dd命令将搜索到新的,经过清理的数据(在本示例中为字节25)的末尾,并删除文件的其余部分。即,它会在那一刻截断文件。


如果由于某种原因该1<>技巧不起作用,则可以执行

sed'/ myregex / d' / path / to / file / filename | dd of = / path / to / file / filename conv = notrunc

另外,请注意,只要您要做的是删除行,您所需要做的就是grep -v myregex(如Barmar所指出的)。


-3

sed -i'd'/路径/到/文件/文件名


1
嗨!最好尽可能详细地说明您的解决方案如何工作并回答问题。
dhag 2015年

2
这是一个非常糟糕的答案。(a)它将在完整的文件系统上失败,就像我的原始命令一样;(b)如果确实成功,它将清空整个文件,而不仅仅是与我的正则表达式匹配的行。
2015年
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.