Why these simple shell commands fail when used in sed's replacement part
当我试图找到这个问题的答案时,我想出了一个我无法理解的奇怪行为。
假设我有一个叫data的文件
1 2 3 4 5
| $> cat data
foo.png
abCd.png
bar.png
baZ.png |
任务是使用sed-in-line将所有行替换为大小写的大写ASCII字符。所以输出应该是:
1 2 3 4 5
| $> cat data
foo.png
abCd.png
bar.png
baZ.png |
解决方案应该在非GNU SED上工作,就像在Mac上一样。
我尝试把这个嵌入式锥子嵌入到塞德的替换部件中:
1
| sed -E 's/[^ ]*[A-Z][^ ]*.png/'$(echo \&|awk '{printf("<%s>[%s]",$0, tolower($0))}')'/' data |
奇怪的是,这输出了:
1 2 3 4
| foo.png
[abCd.png]
bar.png
<baZ.png>[baZ.png] |
正如您所看到的,sed使用大写字母选择了正确的行,这也达到了awk,但是awk的tolower()函数失败,并生成与输入相同的文本。
贝壳专家能解释一下这种奇怪的行为吗?
- 我没有Mac操作系统的经验。另外,我没有BSD SED。你能测试一下这是否适用于你的数据吗?sed 's/./\l&/g'
- 当我打开set -vx并执行您的代码(我必须删除-E,以便我的GNU sed version 4.1.5工作时,我看到sed 's/[^ ]*[A-Z][^ ]*.png/<&>[&]/',这似乎是您问题的根源。抱歉,没有时间探索修复方法。祝你好运。
- @肯特:\l在我的Mac操作系统上不支持SED。
- 可以肯定的是,Mac/BSD SED没有任何方法可以自动执行上下操作,所以除非您想构建一个丑陋的命令行(sed -e s/A/a/g -e s/B/b/g ...,否则您需要另一个工具。
- awk内联解决方案怎么样?awk '{print tolower($0) >FILENAME}' test.in
- @凯文:我其实在找SED的解决方案。但是,如果你把这个awk作为答案,我肯定会投反对票,如果我不能得到任何SED解决方案,我最终也会接受它:)
- 您的SED是否支持"transliterate"命令,即echo abCd.png| sed 'y/ABCD/abcd'?祝大家好运。
- @Shellter:你的SED输出这个:sed: 1:"y/ABCD/abcd": unterminated transform target string。
- @凯文-你要的是鼻涕恶魔,你要写的是你正在读的同一个文件。任何事情都可能发生…如果您想在awk中远程执行类似的操作,您需要在字符串或数组中缓冲输出,然后在结束部分关闭输入文件(),然后打印到该文件。通常只打印到tmp输出文件,然后mv到原始文件更有意义。
- @Anubhava警告说,"解决方案应该在非GNU SED上工作,就像在Mac上工作一样"——这是不可能的,因为非GNU SED不支持"就地编辑"。您真正想要完成的是什么,解决方案可以接受哪些工具?
- @我的Mac OSX 10.6.8上的Edmorton SED支持内联编辑标志-i。
- 那可能是GNU SED。如果您对GNU SED提供的解决方案满意,为什么还要说它必须在非GNU SED中工作?你说它必须在"非GNU SED(如Mac)"中工作,而不是必须在"Mac SED"中工作,所以听起来好像你在寻找一个通用的解决方案。
- @埃德莫顿:我已经知道有关链接问题的\l解决方案,这在我的SEDSOx10.6.8上不受支持。我不在乎我的Mac上是GNU还是非GNU。
- 对不起的!试试echo abCd.png| sed 'y/ABCD/abcd/'…祝大家好运。
- @谢勒:是的,这很有效,我用sed 'y'命令发布了一个答案。
- @阿努巴瓦-所以你想避开的只是\l????希望你能在你的问题上这么说!我以为是江户十一〔十一〕号!
- @埃德莫顿:我并不是想避开\l,我只是想找到一个在osx上工作的可行的内联编辑命令。抱歉,如果我在起草问题时不够清楚。
您的awk命令在sed命令之前运行,而不是作为sed命令的子进程,因此awk只接收一个字符和作为输入,因此它输出
然后,该字符串嵌入到sed作为其参数接收的字符串中,从中可以很明显地看出为什么sed生成它所做的输出。
事件的顺序是
shell看到这个命令行
1
| sed -E 's/[^ ]*[A-Z][^ ]*.png/'$(echo \&|awk '{printf("<%s>[%s]",$0, tolower($0))}')'/' data |
它处理命令替换(其中awk将&转换成<&>[&]来产生中间命令行
1
| sed -E 's/[^ ]*[A-Z][^ ]*.png/'<&>[&]'/' data |
然后,shell使用命令s/[^ ]*[A-Z][^ ]*.png/<&>[&]/执行sed。
- 我不知道替代优先权,谢谢你的解释!
- 实际上,它不会为我输出文字<&>[&]。在我的问题中,我已经显示了SED命令的输出。
- 我在输出中得到这样的行:[abCd.png]。
- awk输出一个文字<&>[&],然后将其并入sed命令,sed用输出中的匹配文本替换每个&。
- @Anubhava:从SED手册页:"替换中出现的符号(`&;'')被匹配re的字符串替换。在此上下文中,"&;"的特殊含义可以通过在其前面加一个反斜杠来抑制。"
- @GordonDavison:谢谢,那么在SED替换中如何使用嵌入式shell命令呢?有可能吗?
- @阿努巴瓦:这是不可能的;SED没有任何命令告诉它启动另一个命令。我想您可能已经输出了一系列的命令,例如awk命令,然后通过管道将其传输到bash来执行输出,但是这将是非常难看的(由于shell元字符等原因,非常脆弱)。
- @戈登戴维斯:够公平的。
1
| sed 'y/ABCDEFGHIJKLMNOPQRSYUVWXYZ/abcdefghijklmnopqrstuvwxyz/' |
- +我相信这是一个很好的答案。
- @阿努巴瓦,你在你的问题中说,解决方案必须在线进行置换。这个解决方案不能做到这一点。这样行吗?如果是这样,您还可以忽略哪些其他要求?
- Ed是对的。这将适用于所有版本的SED。但是-它使每个字符都变为小写-它不只是匹配一个文件名。如果您需要为一行的一部分执行此操作,那么要么需要GNU SED,要么必须使用复杂的SED脚本,将一行拆分为较小的行,然后将它们连接回去。
- @Edmorton:上面的命令可以很容易地内联:sed -i.bak 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' data。
- 布鲁斯-我知道,我的观点是我们不能从行动问题和随后的评论中分辨出他真正想要的是什么,因为他对各种各样不能解决他发布标准的事情都很满意。
也许你真正想要的是tr?
sed当量为:
1
| sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' |
您似乎不能使用字符范围表示法(A-Z和/或[A-Z]),这是不幸和烦人的。
- 我知道tr,要求是:Task is to use sed inline。
- 为什么需要sed,而不是正确的工具?
- 请参阅编辑以获得痛苦冗长的sed等效项。
- @切普纳:这不是我的要求,而是我想回答的相关问题的要求。
您确定不能使用Perl吗?
1
| perl -pi.bak -e 's/([^ ]*[A-Z][^ ]*\.png)/\l\1/' file |
这是反斜杠ell指定小写,反斜杠one重复第一个匹配组。
- +1曾在寻找use sed inline,但Perl绝对是一个不错的选择。
如果您真的需要在普通sed中转换为小写,这是可能的,但相当难看:
1
| sed -e s/A/a/g -e s/B/b/g -e s/C/c/g -e s/D/d/g -e s/E/e/g -e s/F/f/g -e s/G/g/g -e s/H/h/g -e s/I/i/g -e s/J/j/g -e s/K/k/g -e s/L/l/g -e s/M/m/g -e s/N/n/g -e s/O/o/g -e s/P/p/g -e s/Q/q/g -e s/R/r/g -e s/S/s/g -e s/T/t/g -e s/U/u/g -e s/V/v/g -e s/W/w/g -e s/X/x/g -e s/Y/y/g -e s/Z/z/g |
编辑:没关系,@Bruce Barnett的解决方案更好
我敢肯定,如果没有特别难看的东西(sed -e s/A/a/g -e s/B/b/g ...的话,你不能直接在mac/bsd sed上做这件事,因此,除非找到sed解决方案,否则这里有一个awk解决方案,它是内联的:
1
| awk '{print tolower($0) >FILENAME}' data |
- 我知道tr,要求是:Task is to use sed inline。
- @凯文-你要的是鼻涕恶魔,你要写的是你正在读的同一个文件。任何事情都可能发生…如果您想在awk中远程执行类似的操作,您需要在字符串或数组中缓冲输出,然后在结束部分关闭输入文件(),然后打印到该文件。通常只打印到tmp输出文件,然后mv到原始文件更有意义。
基于一些好的答案,这里有一个解决方法,我可以想出:
1 2
| sed -i.bak 'y/'$(awk 'BEGIN {for(i=65; i<=90; i++) printf("%c", i); printf("/");
for(i=97; i<=122; i++) printf("%c", i)}')'/' data |