macos - sed仅替换特定文件的第一个前导空格匹配-处理仅CR的行尾

标签 macos bash awk sed

编者按:
后来为了事后诸葛亮,对标题作了修改;有两个不同的问题:
(a)结果显示,输入文件只有\r-行结尾(仅限CR)(经典的Mac OS样式)
(b)尝试在\tregex中使用\rsed失败,因为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是一种虚拟赋值,其目的是触发输入行的重新生成,每当字段变量被赋值时,都会发生这种情况。
该行通过将字段与输出字段分隔符(默认为单个空格)连接来重建。
实际上,前导空格和尾随空格因此被修剪,每行内部空白都被规范化为一个空格。
如果你想坚持-F1
-即使这意味着一次读取整个文件:
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/

相关文章:

ios - 以编程方式在 Xcode 中获取设备纵横比

python - 在 python shell 中执行 bash 的复杂查找命令

linux - sed 在替换批处理文件中的单词时出现多个选项错误

linux - Linux 上的第一个 top 命令结果可信吗?

cocoa - 获取桌面图标的位置?

cocoa - 以编程方式更改 Mac 上的音量

macos - Mdfind 排除文件夹

arrays - 从数组的所有元素中删除空格

linux - 使用unix脚本发布一条一条地显示和删除记录

linux - Nmap grep 和 awking 结果