带有 "or" 条件的 sed 替换似乎不起作用

sed substitution with an "or" condition doesn't appear to work

当涉及 insert/substitution 时,我的 sed 正则表达式 "or" 似乎不起作用。给定以下数据文件,如果在第五个字段之后存在关键字,我想在关键字之前插入一个回车 return。然后的想法是打印各个行。我知道 Python、Perl 等会更好,但 Bourne shell 是必需的。

data.txt:

field1 field2 field3 field4 field5 first('echo hello') second('ls /tmp')
field1 field2 field3 field4 field5 second('ls -la /home') forth('ls /tmp')
field1 field2 field3 field4 field5 first ('touch /tmp/hello')
field1 field2 field3 field4 field5 fifth('echo hello world') first('ls /etc') third ('mkdir -p /tmp/blah')

script.sh

#!/bin/sh

while read line; do
    oldifs="$IFS"

    scriptlets=$(echo $line | cut -d ' ' -f 6- | sed -e "s=\(first|second|third|forth|fifth\)=\'$'\n=g")
    IFS=$'\n' # this works for Bourne shell 3.2.57
    for scriptlet in $scriptlets; do
        echo "-> $scriptlet"
    done
    IFS="$oldifs"
    echo ""

done < ./data.txt

期望的输出:

-> first('echo hello') 
-> second('ls /tmp')

-> second('ls -la /home') 
-> forth('ls /tmp')

-> first ('touch /tmp/hello')

-> fifth('echo hello world')
-> first('ls /etc')
-> third ('mkdir -p /tmp/blah')

-E 下的 sed 中,分组括号不应该是反斜杠。反斜杠括号与文字匹配。

此外,您对 $scriptlets 的分配缺少命令替换的右括号。此外,您确定要使用命令替换两次,一次在赋值中,一次在 for 循环中?

最后,您的意思可能是 while read line 而不是 for read line,这毫无意义。

这开始是评论,但太长了。请参阅 以获得正确的解决方案。

这里有很多错误。

  1. for read line无效;你可能是说 while read line.
  2. scriptlets=$(... 缺少右括号。
  3. $(scriptlets) 可能不是您想要的 - 您的意思可能是 ${scriptlets}
  4. echo $line 值得怀疑。您可能想引用该变量
  5. Bash 与 Bourne shell 不同,尽管它兼容。例如 Bourne shell 不支持 C 风格的字符串,$'...',如 IFS=$'\n'.
  6. \n是换行符,而\r是回车符return。 (这更像是一个吹毛求疵,但它可能会使阅读问题的人感到困惑。)

尝试使用 ShellCheck 进行调试。

显然,您不能将 sed "or" 和 substitution/insert 组合在一起。因此,我需要将 sed 语句分解为单独的语句。

scriptlets=$(echo $line | cut -d ' ' -f 6- | sed -e 's/ first/\'$'\nfirst/' -e 's/second/\'$'\nsecond/' -e 's/ third/\'$'\nthird/' -e 's/forth/\'$'\nforth/' -e 's/ fifth/\'$'\nfifth/')

默认情况下,sed 使用 "basic" 正则表达式语法,不支持交替(您所说的 "or")。要使用交替,请使用 sed -E 和 "extended" 正则表达式语法。此外,将换行符插入替换模式的语法是乱码。试试这个:

nl=$'\n'
... | sed -E $'s=(first|second|third|forth|fifth)=\\n\1=g' )

但实际上,我建议也以不同的方式处理周围的代码。目前,它从文件中逐行读取,通过 cutsed 传递它们,收集输出,然后使用 for 将其拆分为更多行。为什么不直接通过 cutsed 传递整个文件,然后拆分输出呢?此外,通常最好使用 while read 循环遍历行(因为它不会用 shell 通配符做一些愚蠢的事情)。这个怎么样:

#!/bin/sh

cut -d ' ' -f 6- data.txt | \
    sed -E $'s=(first|second|third|forth|fifth)=\\n\1=g' | \
    while read scriptlet; do
        echo "-> $scriptlet"
    done
echo 

请注意,这会导致循环在子shell 中运行(因为它在管道中)。如果这是一个问题,您需要 bash(不是普通的 sh)及其进程替换功能:

#!/bin/bash

while read scriptlet; do
    echo "-> $scriptlet"
done < <(cut -d ' ' -f 6- data.txt | \
         sed -E $'s=(first|second|third|forth|fifth)=\\n\1=g' )
echo