【问题标题】:POSIX: abcdef to ab bc cd de efPOSIX:abcdef 到 ab bc cd de ef
【发布时间】:2020-07-26 16:29:30
【问题描述】:

使用 POSIX sedawk,我想复制每对相邻字符中的每个第二个字符,并在新行上列出每个新形成的对。

example.txt:

abcd 10001.

预期结果:

ab
bc
cd
d 
 1
10
00
00
01
1.

到目前为止,这就是我所拥有的(注意,如果在 macOS 上省略“--posix”)。出于某种原因,在 \2 之前添加文字换行符 不会产生预期的结果。删除第一组并使用\1 具有相同的效果。我错过了什么?

sed --posix -E -e 's/(.)(.)/&\2\
/g' example.txt

abb
cdd
100
000
1..

【问题讨论】:

  • --posix(仅限 GNU 的选项)和 -E 选项没有意义,因为 -E 在 GNU sed 和 OSX/BSD sed 中启用 ERE,但 ERE 不是t POSIX 支持。也不要假设如果您使用--posix 运行脚本,它只使用 POSIX 指定的语法。这意味着非 POSIX 扩展被禁用,但它可能仍然依赖于 POSIX 根本没有定义的行为,因此它在其他 POSIX seds 中不起作用。基本上,如果您在单行输入或输出上做的不止s/old/new/g,那么您可能正在使用非便携式结构
  • 与 sed 相比,我没有意识到 awk 的便携性。

标签: shell awk sed posix


【解决方案1】:

你可以使用

sed --posix -e 's/./&\
&/g' example.txt | sed '1d;$d'

第一个sed 命令查找字符串中的每个字符并替换为相同的字符,然后是换行符,然后再次替换为相同的字符。由于它替换了第一个和最后一个字符,因此必须删除第一个和最后一个结果行,这可以通过sed '1d;$d' 实现。

如果sed 支持lookarounds,则可以使用(?!^).(?!$)(任何字符,但不在字符串的开头或结尾),最后一个sed 命令将不是必需的,但使用@ 是不可能的987654328@。不过,您可以在 perl 中使用它,perl -pe 's/(?!^).(?!$)/$&\n$&/g' example.txt(请参阅 RHS 中的 demo online$&sed 中的 & 占位符相同,即整个匹配值)。

【讨论】:

  • 不需要另一个sed '1d;$d'。你可以“点”&g; s/^..//; s/..$//'
【解决方案2】:

试试:

$ echo "abcd 10001." | awk '{for(i=1;i<length($0);i++) print substr($0,i,2)}'
ab
bc
cd
d 
 1
10
00
00
01
1.

【讨论】:

  • 我无法想象为什么有人会投反对票,因为这是一个健壮、可移植的解决方案,它可以在任何 shell 中使用任何 awk 来处理所有 UNIX 系统上的任何输入。
【解决方案3】:

使用 GNU awk,请您尝试关注。使用显示的示例进行编写和测试,并在链接中进行测试 https://ideone.com/qahp0S

awk '
BEGIN{
  FS=""
}
{
  for(i=1;i<=(NF-1);i++){
    print $i$(i+1)
  }
}
' Input_file

说明:在程序的 BEGIN 部​​分将字段分隔符设置为 NULL,用于此处的所有行。然后在主程序中运行一个从第一个字段到最后一个字段的 for 循环。在该循环的每次迭代中打印当前和下一个字段。

【讨论】:

    【解决方案4】:

    使用相同的例程,可以在bash 本身中完成:

    s='abcd 10001.'
    
    for((i=0; i<${#s}-1; i++)); do echo "${s:i:2}"; done
    
    ab
    bc
    cd
    d
     1
    10
    00
    00
    01
    1.
    

    【讨论】:

      【解决方案5】:

      只是为了好玩,一个 sed 包含 3 个替换:

      $ echo "abcd 10001." | sed 's/./&&/g;s/\(^.\|.$\)//g;s/../&\n/g'
      

      第一部分复制所有字符,第二部分删除第一个和最后一个字符,第三部分在每个字符对之后添加一个换行符。

      如果您想符合 POSIX,您必须这样做:

      $ echo "abcd 10001." | sed -e  's/./&&/g' -e 's/^.//g' -e 's/.$//g' -e 's/../&\n/g'
      

      这里我们必须添加一个额外的表达式,因为表达式 \(^.\|.$) 是一个 ERE,而 posix sed 只接受一个 BRE

      【讨论】:

      • 只是一个细节,可以使用echo -n ... 来兼容 OP 案例,而不是由 sed 获取和管理/输出换行符? (awk 不会出现问题)
      • @bruno 在 Posix 中,没有选项 -n
      【解决方案6】:

      这可能对你有用(GNU sed):

      sed 's/.\(.\)/&\n\1/;/../P;D' file
      

      用前两个字符、换行符和第二个字符替换前两个字符。

      如果第一行有两个字符,则打印第一行,删除第一行并重复。

      替代的,更冗长的:

      sed -E ':a;s/^(([^\n]{2}\n)*[^\n])([^\n])([^\n])/\1\3\n\3\4/;ta' file
      

      或者,没有硬编码的新行:

      sed -E '/.../{G;s/^(.(.))(.*)(.)/\1\4\2\3/;P;D}' file
      

      最后:

      sed 's/./&\n&/g;s/^..\|..$/g' file
      

      【讨论】:

        【解决方案7】:

        POSIX 未指定进程替换。 POSIX 要求仅针对awksed 指定,所以也许下一个解决方案是可以接受的:

        paste -d '\0' <(echo; fold -w1 example.txt) <(fold -w1 example.txt) | grep ..
        

        while read -n1 ch; do
           printf "%s\n%s" "${ch}" "${ch}"
        done < example.txt | grep ..
        

        sed 's/./&&/g;s/.//' example.txt | grep -o ..
        

        【讨论】:

        • paste -d "" 是 GNU 主义。对于相同的功能,POSIX 将是 paste -d '\0'。然后您需要先删除并与| sed -e '1d;$d' 对齐以获取 OP 示例。不过很有创意!
        • @dawg 我编辑了我的答案(添加了 \0)。我通过使用两个字符来删除第一行/最后一行。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-22
        • 1970-01-01
        • 2019-06-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-09
        相关资源
        最近更新 更多