我正在尝试从文本文件中检索具有如下元数据的特定字段:
project=XYZ; cell=ABC; strain=C3H; sex=F; age=PQR; treatment=None; id=MLN
我有以下脚本来检索字段'cell'
while read line
do
cell="$(echo $line | cut -d";" -f7 )"
echo $cell
fi
done < files.txt
但是,以下脚本将整个字段检索为cell=ABC
,而我只想从字段中获取值'ABC'
,如何在同一行代码中检索正则表达式之后的值?
答案 0 :(得分:19)
如果提取一个值(或者,通常是由不同捕获组捕获的非重复值集合)就足够了,并且您正在运行 { {1}},bash
或ksh
,请考虑使用正则表达式匹配运算符zsh
:=~
:< / p>
向@AdrianFrühwirth提出[[ string =~ regex ]]
和ksh
解决方案要点的提示。
示例输入字符串:
zsh
接下来讨论特定于Shell的string='project=XYZ; cell=ABC; strain=C3H; sex=F; age=PQR; treatment=None; id=MLN'
用法;通过shell函数的=~
功能的多shell实现可以在最后找到。
特殊=~
数组变量接收匹配操作的结果:元素BASH_REMATCH
包含整个匹配,元素0
包含第一个捕获组(带括号的子表达式)匹配,依此类推
1
:
bash 3.2+
[[ $string =~ \ cell=([^;]+) ]] && cell=${BASH_REMATCH[1]} # -> $cell == 'ABC'
:
虽然上面的特定命令有效,但在bash bash 4.x
中使用正则表达式文字是有缺陷的,特别是在Linux上涉及字边界断言4.x
和\<
时;例如,\>
莫名其妙地不匹配;解决方法:使用中间变量(未引用!):[[ a =~ \<a ]]
有效(也适用于bash re='\a'; [[ a =~ $re ]]
)。
3.2+
- 或者在设置bash 3.0 and 3.1
之后:
引用正则表达式使其工作:
shopt -s compat31
[[ $string =~ ' cell=([^;]+)' ]] && cell=${BASH_REMATCH[1]} # -> $cell == 'ABC'
语法与ksh
中的语法相同,除了:
bash
(即使仅使用.sh.match
隐式引用第一个元素,也必须将名称括在{...}
中: ${.sh.match}
[[ $string =~ \ cell=([^;]+) ]] && cell=${.sh.match[1]} # -> $cell == 'ABC'
语法也类似于bash,除了:
zsh
仅需要出于语法原因引用,并始终将结果字符串作为一个整体视为正则表达式,无论是引用它还是部分引用或不;
包含整个匹配的字符串zsh
仅包含捕获组的匹配项(请注意$MATCH
数组以索引$match
开头,并且您不需要在{{{{}}中包含变量名称1}}引用数组元素)zsh
1
运算符的多shell实现,作为shell函数{...}
以下shell函数抽象了 [[ $string =~ ' cell=([^;]+)' ]] && cell=$match[1] # -> $cell == 'ABC'
,=~
,reMatch
与bash
运算符之间的差异;匹配在数组变量ksh
中返回。
正如@AdrianFrühwirth所指出的那样,要编写便携式(zsh
,=~
,${reMatches[@]}
)代码,您需要在{{1}中执行zsh
使其数组以索引 ksh
开头;作为副作用,您还必须在引用数组时使用bash
语法,如setopt KSH_ARRAYS
和zsh
)。
应用于我们的示例,我们得到:
0
外壳功能:
${...[]}
注意:
ksh
(与bash
相对)用于声明函数, # zsh: make arrays behave like in ksh/bash: start at *0*
[[ -n $ZSH_VERSION ]] && setopt KSH_ARRAYS
reMatch "$string" ' cell=([^;]+)' && cell=${reMatches[1]}
真正使用# SYNOPSIS
# reMatch string regex
# DESCRIPTION
# Multi-shell implementation of the =~ regex-matching operator;
# works in: bash, ksh, zsh
#
# Matches STRING against REGEX and returns exit code 0 if they match.
# Additionally, the matched string(s) is returned in array variable ${reMatch[@]},
# which works the same as bash's ${BASH_REMATCH[@]} variable: the overall
# match is stored in the 1st element of ${reMatch[@]}, with matches for
# capture groups (parenthesized subexpressions), if any, stored in the remaining
# array elements.
# NOTE: zsh arrays by default start with index *1*.
# EXAMPLE:
# reMatch 'This AND that.' '^(.+) AND (.+)\.' # -> ${reMatch[@]} == ('This AND that.', 'This', 'that')
function reMatch {
typeset ec
unset -v reMatch # initialize output variable
[[ $1 =~ $2 ]] # perform the regex test
ec=$? # save exit code
if [[ $ec -eq 0 ]]; then # copy result to output variable
[[ -n $BASH_VERSION ]] && reMatch=( "${BASH_REMATCH[@]}" )
[[ -n $KSH_VERSION ]] && reMatch=( "${.sh.match[@]}" )
[[ -n $ZSH_VERSION ]] && reMatch=( "$MATCH" "${match[@]}" )
fi
return $ec
}
创建局部变量时需要该函数。答案 1 :(得分:3)
我不会使用cut
,因为您无法指定多个分隔符。
如果您的grep
支持PCRE
,则可以执行以下操作:
$ string='project=XYZ; cell=ABC; strain=C3H; sex=F; age=PQR; treatment=None; id=MLN'
$ grep -oP '(?<=cell=)[^;]+' <<< "$string"
ABC
您可以使用sed
,简单来说可以将其作为 -
$ sed -r 's/.*cell=([^;]+).*/\1/' <<< "$string"
ABC
另一种选择是使用awk
。有了它,您可以通过指定要作为字段分隔符考虑的分隔符列表来执行以下操作:
$ awk -F'[;= ]' '{print $5}' <<< "$string"
ABC
你可以通过遍历该行来进行更多检查,这样就不必硬编码来打印第5个字段。
请注意,如果您的shell不支持here-string表示法<<<
,那么您可以echo
变量并将其传递给命令。
$ echo "$string" | cmd
答案 2 :(得分:2)
这是一个本机shell解决方案:
$ string='project=XYZ; cell=ABC; strain=C3H; sex=F; age=PQR; treatment=None; id=MLN'
$ cell=${string#*cell=}
$ cell=${cell%%;*}
$ echo "${cell}"
ABC
这会从字符串中删除包含cell=
的最短前导匹配,然后移除最长的跟踪匹配,直至包含;
,并留下ABC
。
这是另一个使用read
分割字符串的解决方案:
$ cat t.sh
#!/bin/bash
while IFS=$'; \t' read -ra attributes; do
for foo in "${attributes[@]}"; do
IFS='=' read -r key value <<< "${foo}"
[ "${key}" = cell ] && echo "${value}"
done
done <<EOF
foo=X; cell=ABC; quux=Z;
foo=X; cell=DEF; quux=Z;
EOF
$ ./t.sh
ABC
DEF
对于使用外部工具的解决方案,请参阅@ jaypal的优秀答案。