带有 "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
,这毫无意义。
这开始是评论,但太长了。请参阅 以获得正确的解决方案。
这里有很多错误。
for read line
无效;你可能是说 while read line
.
scriptlets=$(...
缺少右括号。
$(scriptlets)
可能不是您想要的 - 您的意思可能是 ${scriptlets}
echo $line
值得怀疑。您可能想引用该变量
- Bash 与 Bourne shell 不同,尽管它兼容。例如 Bourne shell 不支持 C 风格的字符串,
$'...'
,如 IFS=$'\n'
.
\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' )
但实际上,我建议也以不同的方式处理周围的代码。目前,它从文件中逐行读取,通过 cut
和 sed
传递它们,收集输出,然后使用 for
将其拆分为更多行。为什么不直接通过 cut
和 sed
传递整个文件,然后拆分输出呢?此外,通常最好使用 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
当涉及 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
,这毫无意义。
这开始是评论,但太长了。请参阅
这里有很多错误。
for read line
无效;你可能是说while read line
.scriptlets=$(...
缺少右括号。$(scriptlets)
可能不是您想要的 - 您的意思可能是${scriptlets}
echo $line
值得怀疑。您可能想引用该变量- Bash 与 Bourne shell 不同,尽管它兼容。例如 Bourne shell 不支持 C 风格的字符串,
$'...'
,如IFS=$'\n'
. \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' )
但实际上,我建议也以不同的方式处理周围的代码。目前,它从文件中逐行读取,通过 cut
和 sed
传递它们,收集输出,然后使用 for
将其拆分为更多行。为什么不直接通过 cut
和 sed
传递整个文件,然后拆分输出呢?此外,通常最好使用 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