关于macos:为什么这些简单的shell命令在sed的替换部分中使用时会失败

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()函数失败,并生成与输入相同的文本。

贝壳专家能解释一下这种奇怪的行为吗?


您的awk命令在sed命令之前运行,而不是作为sed命令的子进程,因此awk只接收一个字符和作为输入,因此它输出

1
<&>[&]

然后,该字符串嵌入到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


  • 1
    sed 'y/ABCDEFGHIJKLMNOPQRSYUVWXYZ/abcdefghijklmnopqrstuvwxyz/'


    也许你真正想要的是tr

    1
    tr A-Z a-z file

    sed当量为:

    1
    sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'

    您似乎不能使用字符范围表示法(A-Z和/或[A-Z]),这是不幸和烦人的。


    您确定不能使用Perl吗?

    1
    perl -pi.bak -e 's/([^ ]*[A-Z][^ ]*\.png)/\l\1/' file

    这是反斜杠ell指定小写,反斜杠one重复第一个匹配组。


    如果您真的需要在普通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


    基于一些好的答案,这里有一个解决方法,我可以想出:

    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