编者按:
后来为了事后诸葛亮,对标题作了修改;有两个不同的问题:
(a)结果显示,输入文件只有\r
-行结尾(仅限CR)(经典的Mac OS样式)
(b)尝试在\t
regex中使用\r
和sed
失败,因为BSD Sed(在OSX上使用)不支持这种转义。
我正在开发一个自动程序,它使用Python查找和替换文本文件中的某些单词。程序使用字典,有些实例中用作替换的值是''
(意思是,没有)。我不认为这个程序引起了这个问题,但我只是在上下文中提到这个问题。(我认为问题在于sed
,所以我不愿意标记Python。)
文件中的某些行有前导空白,这些空白是在文件开头的某些单词被空替换后无意中创建的。我想摆脱他们,我认为在这种情况下sed
是最好的工具。
假设文本文件是这样的:
Display
Display
BOX,
所以我使用以下命令通过
sed
运行编辑过的文件:sed -e 's/^[ \t]*//g'
结果是:
Display
Display
BOX,
只编辑第一个匹配项。为什么?
通过测试,我创建了一个全新的纯文本文件,如下所示:
hello
hello
hello
然后我对它运行上面的命令。这确实如预期的那样有效。为什么?
是否可能使用了其他形式的空间(不可打印字符?)是由Python程序创建的?但为什么
sed
至少要工作一次呢?顺便说一句,我正在开发另一个与OS X兼容的便携式解决方案或工具,用于修剪纯文本文件中每一行的前导空白。
编辑:这里是文件的一些
xxd
输出(用X替换了大多数实际内容):0000000: 2044 6973 706c 6179 2043 616c 6962 7261 X X
0000010: 7469 6f6e 2046 6978 7475 7265 2046 4952 X X X
0000020: 4d57 4152 4520 4b49 545e 4d20 4469 7370 X X^M X
0000030: 6c61 7920 4361 6c69 6272 6174 696f 6e20 X X
0000040: 4669 7874 7572 6520 524d 6163 426f 6f6b X X
0000050: 2041 6972 2028 3131 2d69 6e63 682c 204d X X
0000060: 6964 2032 3031 3229 2050 4f52 5420 4b49 X X) X X
0000070: 545e 4d42 4f58 2c20 5245 434f 5645 5259 T^MBOX, X
最佳答案
tl;博士
下面的任何解决方案都不会更新输入文件;独立的sed
命令可以使用-i ''
进行调整;需要先将awk
解决方案保存到其他文件。
操作系统的输入似乎是一个只有换行符的经典Mac OS文件
谢谢,阿尔维斯。
.\r
总是读取这样一个文件作为一个整体,这通常是不需要的,并且妨碍了OP的行前导空白裁剪方法。sed
因此是更好的选择,因为它允许指定什么构成换行符(通过所谓的输入记录分隔符):
更新:将原来的awk
命令替换为更简单、更快的替代命令,改编自peak's solution:
awk -v RS='\r' '{ sub(/^[ \t]+/, ""); print }'
如果还可以从每一行中修剪尾随空格(如果有的话),并将每一行中单词之间的空格规格化为一个空格,则可以简化为:
awk -v RS='\r' '{ $1=$1; print }'
请注意,输出线将被
awk
分开,这是通常需要的。有关解释和背景信息,包括如何将
\n
保留为换行符,请继续阅读。注:答案的第一部分一般适用,但假设输入有以
\r
结尾的行;OP的特殊情况(行显然仅以\n
结尾)在第二部分中处理。在OSX上使用的BSD Sed只支持
\r
作为控制字符转义序列;因此,\n
用于匹配制表符。不支持。要仍然匹配制表符,可以拼接ANSI C-quoted string以生成实际的制表符。在Sed脚本中(
\t
):sed 's/^[ '$'\t'']*//'
在这个简单的例子中,可以对整个Sed脚本(
$'\t'
)使用ansic引用的字符串,但对于更复杂的脚本,这可能会变得棘手,因为这些字符串有自己的转义规则。请注意,选项
sed -e $'s/^[ \t]*//'
已被删除,因为它是无意义的,因为regex被锚定到输入的开头(g
)。关于GNU和BSD-Sed之间差异的总结,请参见我的this answer。
正如@alvits在注释中指出的,输入文件实际上可能有
^
实例,而不是Sed需要分隔行的\r
实例。也就是说,该文件可能具有OSX Mac OS之前的行终止符:一个
\n
\r
终止一个行。一种简单的验证方法是将输入文件传递给
by itself
:将cat -et
实例可视化为\r
,而将^M
实例可视化为\n
(另外,$
实例可视化为\t
)。如果输出中只有
^I
个实例,但没有^M
个实例,则意味着行不会以$
结尾(同时),整个输入文件被视为单个字符串,这就解释了为什么只处理第一个输入“行”:\n
只在整个字符串的最开始处匹配。由于Sed解决方案(不进行预处理)会导致整个文件作为一个整体被读取,因此
^
是更好的选择:要按照类Unix平台上的惯例创建分离的输出:
awk -v RS='\r' '{ sub(/^[ \t]+/, ""); print }'
awk
告诉Awk通过\n
实例将输入拆分为记录(特殊变量-v RS='\r'
包含输入记录分隔符)。\r
搜索输入行上第一个出现的regexRS
,并将其替换为sub(/^[ \t]+/, "")
,即,它有效地修剪了每个输入行的前导空格和制表符。注意,不带显式第三个参数的^[ \t]+
隐式操作整个输入行。""
然后打印可能修改的输入行。由于
sub()
是Awk的默认输出记录分隔符($0
),输出记录将被print
终止。如果您真的想保留
\n
作为行分隔符:awk 'BEGIN { RS=ORS="\r" } { sub(/^[ \t]+/, ""); print }'
OFS
将输入和输出记录分隔符设置为\n
。如果还可以从每一行中删去尾随空格(如果有的话),并将每一行中单词之间的空格规格化为一个空格,则可以将以
\r
结尾的变量简化为:awk -v RS='\r' '{ $1=$1; print }'
不使用
RS=ORS="\r"
(也不设置\r
,脚本中的输入字段分隔符)意味着Awk通过运行空格(空格、制表符、换行符)将输入记录拆分为字段。\n
是一种虚拟赋值,其目的是触发输入行的重新生成,每当字段变量被赋值时,都会发生这种情况。该行通过将字段与输出字段分隔符(默认为单个空格)连接来重建。
实际上,前导空格和尾随空格因此被修剪,每行内部空白都被规范化为一个空格。
如果你想坚持
-F
1-即使这意味着一次读取整个文件:
sed $'s/^[ \t]*//; s/\r[ \t]*/\\\n/g' # note the $'...' to make \t, \r, \n work
这将输出以
FS
结尾的行,这在Unix上是常见的。相比之下,如果您希望保留
$1=$1
作为行分隔符,请使用以下内容-但请注意,BSD Sed将始终在最后添加一个OFS
。 sed $'s/^[ \t]*//; s/\r[ \t]*/\r/g'
[1]peak's answer最初更清楚地显示了一个实用的多用途替代方案:使用
sed
将所有\n
实例替换为\r
实例,并将结果传递到原始\n
命令的BSD Sed友好版本:\r
关于macos - sed仅替换特定文件的第一个前导空格匹配-处理仅CR的行尾,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35327981/