在 bash 脚本中,我有以下变量:
file_name='this_is_the_hart_part.csv'
使用
var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')
我想提取子字符串“the”(变量 $file_name 中的下划线数字 2 和 3 之间)。
但是我得到的 $var2 等于 $file_name。我该如何更改我的 sed 命令?
答案1
支持的正则表达式类型sed
不允许与 进行非贪婪匹配*
。
您想要获得第三个_
分隔字段。这是最简单的方法cut
:
cut -d '_' -f 3
或者,与awk
:
awk -F '_' '{ print $3 }'
或者,在 shell 中,连续删除前两个此类字段,然后修剪末尾:
str=${file_name#*_}
str=${str#*_}
str=${str%%_*}
"$str"
就是the
最后这个词。使用最后一个变体可能是这三种方法中最快、最可靠的方法。
变量替换${variable#*_}
将产生一个字符串,该字符串的$variable
前导位直到并包括第一个下划线被删除。将${variable%%_*}
删除从第一个下划线到结尾的所有内容$variable
。这些是标准变量替换。
在文件名上使用变量替换的好处是它可以处理包含换行符的文件名,而 或awk
或sed
都cut
不会。一般来说,不要对文件名使用面向行的文本编辑工具。
此外,您正在使用echo $file_name
.由于$file_name
未加引号,因此它将经历单词分割(默认情况下,在也是空格、制表符和换行符的每个字符上$IFS
),并且生成的单词(如果它们包含文件名通配字符)将与当前目录中的文件名进行匹配通过外壳。文件名中的反斜杠也可能会消失或产生不需要的效果(即使您引用扩展名)。当未加引号时,shellksh
还会对 的值进行大括号扩展。$file_name
答案2
首先要注意的sed
是文本默认情况下一次只处理一行的实用程序,而文件名可以包含任何字符(包括换行符)甚至非字符(可以是非字符)文本)。
还,不加引号的变量有非常特殊的含义,你几乎不想这样做,这也是潜在非常危险。
另外,类似 Bourne 的 shell 中的变量赋值语法是:var=value
,而不是$var=value
。
echo
您可以使用以下命令将(或更好的是printf
)的整个输出加载到sed
的模式空间中:
printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'
然后,您可以添加代码来提取第二个和第三个之间的部分_
:
var2=$(
printf '%s\n' "$filename" |
sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)
非贪婪部分是通过使用[^_]*
(一系列非_
字符)来解决的,这与.*
保证我们不匹配过去的_
边界相反(尽管在许多实现中它仍然会被非字符阻塞)。
在这种情况下,您可以改用 shell 参数扩展运算符:
case $filename in
(*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
(*) var2=;;
esac
如果文件名不是文本或者您想要提取的部分以换行符结尾(并且也会更有效),则效果会更好。
有些 shell 喜欢zsh
或ksh93
有更高级的运算符:
zsh
:拆分
_
并获取第三个字段:var2=${"${(@s:_:)filename}"[3]}
使用
${var/pattern/replacement}
和 反向引用(在这种情况下,您需要首先验证变量是否包含至少 3 个下划线,否则不会有任何替换)。set -o extendedglob var2=${filename/(#b)*_*_(*)_*/$match[1]}
ksh93
:var2=${filename/*_*_@(*)_*/\1}
答案3
@Kusalananda 是对的,这sed
是错误的工具,你不能进行非贪婪匹配。但是您可以使用非贪婪匹配的解决方法:
[^_]*
将匹配任何不是的字符_
所以在你的情况下你可以这样做:
printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'
但是...对于您的用例,您应该更好地使用其他工具...