Awk 如何在Unix中从文件名中提取具有已知结构的子字符串?
我有一堆复杂的文件名,其中包含很多信息,我试图从每个文件名中提取两个子字符串 名称具有以下结构:Awk 如何在Unix中从文件名中提取具有已知结构的子字符串?,awk,grep,Awk,Grep,我有一堆复杂的文件名,其中包含很多信息,我试图从每个文件名中提取两个子字符串 名称具有以下结构: ADNI_002_S_0295_MR_MT1__N3m_Br_20110623105302806_S110476_I241350_RightHippoSubfields.mgz.txt ADNI_002_S_1155_MR_MT1__GradWarp__N3m_Br_20120322164018368_S97230_I291880_RightHippoSubfields.mgz.txt ADNI_0
ADNI_002_S_0295_MR_MT1__N3m_Br_20110623105302806_S110476_I241350_RightHippoSubfields.mgz.txt
ADNI_002_S_1155_MR_MT1__GradWarp__N3m_Br_20120322164018368_S97230_I291880_RightHippoSubfields.mgz.txt
ADNI_002_S_0729_MR_MT1__N3m_Br_20120913163818876_S159861_I334105_RightHippoSubfields.mgz.txt
从每个文件名中,我想在Unix shell中提取:
?\u S\u???
的字符串(如002\u S\u 0295
,002\u\u 115
,002\u 0729
)\u I
和下一个下划线之间的数字(如241350
,291880
,334105
)我尝试过grep和awk的一些组合,但实际上我无法想出解决方案。如果将文件名存储在文件
文件中,您可以执行以下操作:
1.-形式为???S???的字符串????(如002__0295、002__115、002__0729)
对于任何支持ERE的sed:
$ sed -E 's/.*_([^_]+_S_[^_]+).*_I([^_]+).*/\1 \2/' file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105
对于任何POSIX sed:
$ sed 's/.*_\([^_]*_S_[^_]*\).*_I\([^_]*\).*/\1 \2/' file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105
使用GNU awk匹配第三个参数()
您可以在Bash中使用正则表达式(假设它是您的shell)来执行以下操作:
while read -r line || [[ -n $line ]]; do
printf "'%s'\n" "$line"
if [[ "$line" =~ (.{3}_S_.{4}) ]]
then
echo ${BASH_REMATCH[1]}
fi
if [[ "$line" =~ _I([0-9]+) ]]
then
echo ${BASH_REMATCH[1]}
fi
echo
done <file
这可以通过Bash中的扩展模式匹配来实现——诚然,这有点复杂:
shopt -s extglob
patterns=('???_S_????' '_I+([!_])')
for fname in *.mgz.txt; do
for pat in "${patterns[@]}"; do
var=${fname#${fname%$pat*}}
var=${var%${var##$pat}}
echo "${var#_I}"
done
done
这将使用嵌套的参数扩展来删除部分文件名。第一个文件和第一个模式的示例:
- 删除模式之前的部分文件名:
${fname%$pat*}
扩展为${fname%?\u S_???*}
,因此它删除了从模式到名称末尾的所有内容,从而产生ADNI
- 现在在
${fname#${fname%$pat*}
中重新使用该结果,它将成为${fname#ADNI}
,扩展为
002_S_0295_MR_MT1__N3m_Br_20110623105302806_S110476_i24135; u rightshipposubfields.mgz.txt
因此,var
现在具有文件名中以模式开头的部分
- 正在删除模式后的部分文件名:
${var###$pat}
扩展为${var#####S#}
,从文件名的开头删除模式<第一个模式不需要code>##
(最长匹配),但第二个模式需要1:+([!])
是“一个或多个非下划线字符”,我们需要最长匹配。这种扩展的结果是
6_S110476_I241350_rightshipposubfields.mgz.txt
i、 例如,var
中我们要删除的部分
${var%${var###$pat}
展开为
${var%6_S110476_I241350_rightshipposubfields.mgz.txt}
这将删除模式之后的所有内容\u I
,因此我们使用
echo“${var#u I}”
要移除它。对于第一个模式,这是一个no-op2,对于第二个模式,它删除了\u I
002\u S\u 0295
241350
002_S_0729
334105
002_S_1155
291880
1
+()
模式也是需要extglob
的原因
2如果
???\u S_???
恰好与以\u I
开头的字符串匹配,则这将导致不必要的删除,但基于示例文件名,则不会。请准确显示所需的输出。您希望每个名称的两部分包含在两个变量中,还是一个字符串中?用一个字符分隔各个部分?请阅读关于如何创建MCVE()——其中一个关键部分是所需的输出。另一个关键部分是展示你的最大努力。对不起,我以为我已经包含了所有必需的元素,但我错了。谢谢你的建议,我以后再问的时候会更仔细、更彻底。在这个问题上补充缺失的信息还不算太晚。@fedorqui:这正是我想要的。非常感谢!您应该提到,grep的-P
选项仅为GNU,并且用手册页上的话来说,具有高度的实验性
,因此使用它是非常困难的。即使它起作用,由于PCRE的评估方式,它也会非常缓慢。就我个人而言,我会避免这样做。
$ awk 'match($0,/([^_]+_S_[^_]+).*_I([^_]+)/,a) { print a[1], a[2] }' file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105
while read -r line || [[ -n $line ]]; do
printf "'%s'\n" "$line"
if [[ "$line" =~ (.{3}_S_.{4}) ]]
then
echo ${BASH_REMATCH[1]}
fi
if [[ "$line" =~ _I([0-9]+) ]]
then
echo ${BASH_REMATCH[1]}
fi
echo
done <file
'ADNI_002_S_0295_MR_MT1__N3m_Br_20110623105302806_S110476_I241350_RightHippoSubfields.mgz.txt'
002_S_0295
241350
'ADNI_002_S_1155_MR_MT1__GradWarp__N3m_Br_20120322164018368_S97230_I291880_RightHippoSubfields.mgz.txt'
002_S_1155
291880
'ADNI_002_S_0729_MR_MT1__N3m_Br_20120913163818876_S159861_I334105_RightHippoSubfields.mgz.txt'
002_S_0729
334105
awk -F'[_I]' '{print $3,$4,$5" "$(NF-1)}' OFS=_ file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105
shopt -s extglob
patterns=('???_S_????' '_I+([!_])')
for fname in *.mgz.txt; do
for pat in "${patterns[@]}"; do
var=${fname#${fname%$pat*}}
var=${var%${var##$pat}}
echo "${var#_I}"
done
done