给定一个形如 someletters_12345_moreleters.ext
的文件名,我想提取其中的 5 个数字并将其赋值给一个变量。
为了强调一下,我有一个由 x 个字符组成的文件名,然后是由单个下划线包围的五位数字序列,在另一组 x 个字符之后。我想要取出这个五位数并将其存入一个变量中。
我非常感兴趣的是有多少种方法可以实现这个目标。
b=${a:12:5}
其中12是偏移量(从零开始),5是长度。
如果输入中下划线是唯一的字符,则可以通过两个步骤去除前缀和后缀:
tmp=${a#*_} # remove prefix ending in "_"
b=${tmp%_*} # remove suffix starting with "_"
如果有其他下划线,可能仍然可行,但会更加棘手。如果有人知道如何在单个表达式中执行两个扩展,我也想知道。
所提供的两个解决方案都是纯bash,没有涉及进程生成,因此非常快。
bash: ${${a#*_}%_*}: bad substitution
的错误提示。 - JB.sh
脚本中运行它,这可能是破折号。现在我无法让它再次工作。 - Spencer Rathbun:-
。因此,${a: -12:5}
会输出距离末尾12个字符的5个字符,而${a: -12:-5}
会输出末尾倒数第12个字符到倒数第5个字符之间的7个字符。 - JB.使用 cut 命令:
echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2
更加通用:
INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
echo
的参数周围正确使用双引号。更多信息请参见https://dev59.com/NWkw5IYBdhLWcg3wMHum。 - tripleee尝试使用cut -c 开始索引-结束索引
startIndx-$((lastIndx-1))
。 - brown.2179start=5;stop=9; echo "the rain in spain" | cut -c $start-$(($stop-1))
- brown.2179git log --oneline | head -1 | cut -c 9-(end -1)
。 - Niklascut
来完成,如 line=
git log --oneline | head -1 && echo $line | cut -c 9-$((${#line}-1))
,但在这种特定情况下,最好使用 sed ,如 git log --oneline | head -1 | sed -e 's/^[a-z0-9]* //g'
。 - brown.2179number=$(echo "$filename" | egrep -o '[[:digit:]]{5}' | head -n1)
number="${filename:offset:length}"
stuff_digits_...
,您可以使用awk:number=$(echo "$filename" | awk -F _ '{ print $2 }')
number=$(echo "$filename" | tr -cd '[[:digit:]]')
echo $filename |
is itself broken -- it should be echo "$filename" | ...
. See I just assigned a variable, but echo $variable
shows something else!. Or, for a bash-only more-efficient approach (at least, more efficient if your TMPDIR is stored on tmpfs, as is conventional on modern distros), <<<"$filename" egrep ...
- Charles Duffyegrep
е·Іиў«ејѓз”ЁпјЊиЇ·дЅїз”Ёgrep -E
д»Јж›їгЂ‚ - soundflix这是我会做的方式:
FN=someletters_12345_moreleters.ext
[[ ${FN} =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}
解释:
针对Bash:
[[ ]]
表示条件表达式=~
表示条件是一个正则表达式&&
串联命令,当先前的命令成功时正则表达式(RE):_([[:digit:]]{5})_
_
是字面字符,用于标记匹配边界()
创建一个捕获组[[:digit:]]
是一个字符类,我认为这个已经很清楚了{5}
意味着之前的字符、类别(本例中)或组必须恰好匹配五次可以将其视为以下英文行为:迭代FN
字符串的每个字符,直到我们看到一个_
,此时捕获组被打开,并尝试匹配五个数字。如果到目前为止匹配成功,则捕获组保存遍历的五个数字。如果下一个字符是_
,则条件成功,捕获组在BASH_REMATCH
中可用,并且下一个NUM=
语句可以执行。如果任何匹配的部分失败,则保存的细节将被处理并且字符处理将在_
之后继续进行。例如,如果FN
为_1 _12 _123 _1234 _12345_
,则在找到匹配项之前会出现四次错误尝试。
cut
生效)。它也不依赖于执行外部命令。 - Dan Dascalescu[[:digit:]]
而不是[0-9]
?我是否忽略了某些区域设置问题?我认为数字组始终应该是[0-9],而[0-9]仍然比[[:digit:]]更清晰、更节省空间。我一直对这个字符类的存在有点好奇。当它可以用\d
表示时,我理解它,但不理解它的完整形式。这与非扩展正则表达式有关吗? - UrsineRaven如果有人需要更为严谨的信息,您也可以像这样在 man bash 中搜索
$ man bash [press return key]
/substring [press return key]
[press "n" key]
[press "n" key]
[press "n" key]
[press "n" key]
结果为:
${parameter:offset}
${parameter:offset:length}
子字符串扩展。从偏移量所指定的字符开始,将参数的最多length个字符扩展出来。如果省略了length,则将参数的子字符串从指定偏移处开始扩展出来。length和offset是算术表达式(参见下面的算术计算)。如果offset计算为一个小于零的数字,则该值被用作从参数值结尾处的偏移量。以-开头的算术表达式必须与前面的冒号用空格分隔,以便与默认值扩展进行区分。如果length计算为一个小于零的数字,并且parameter不是@也不是一个索引或关联数组,则将其解释为从parameter值的结尾处的偏移量而不是字符数,扩展为两个偏移之间的字符。如果参数是@,则结果是从offset开始的长度位置参数。如果参数是由@或*订阅的索引数组名称,则结果是以${parameter[offset]}开头的数组成员的长度。负偏移相对于指定数组的最大索引加1。应用于关联数组的子字符串扩展会产生未定义的结果。请注意,负偏移必须与冒号至少用一个空格分隔,以避免与:-扩展混淆。除非使用位置参数,则子字符串索引从0开始。如果偏移为0且使用了位置参数,则$0将作为列表的前缀。${var: -4}
。 - sshow${var:0-4}
。 - Vopel我很惊讶这个纯Bash解决方案没有被提出:
a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345
您可能希望将IFS重置为先前的值,或在之后取消设置IFS
!
IFS
和位置参数:IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
。这样写也可以达到同样的效果,并且更为简洁易懂。 - kojiro在 jor 的答案的基础上进行改进(但对我来说并不起作用):
substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
[:digit:] *
代替[^ _] *
? - YoavKlein如果我们聚焦于这个概念:
"一串(一个或多个)数字"
我们可以使用几个外部工具来提取数字。
我们可以使用sed或tr轻松地删除所有其他字符:
name='someletters_12345_moreleters.ext'
echo $name | sed 's/[^0-9]*//g' # 12345
echo $name | tr -c -d 0-9 # 12345
但如果 $name 包含多个数字序列,上述方法会失败:
比如当 "name=someletters_12345_moreleters_323_end.ext" 时,会出现问题:
echo $name | sed 's/[^0-9]*//g' # 12345323
echo $name | tr -c -d 0-9 # 12345323
我们需要使用正则表达式 (regex)。
在 sed 和 perl 中只选择第一次运行 (12345 而不是 323):
echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/'
perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";'
但我们也可以直接在 bash(1) 中完成:
regex=[^0-9]*([0-9]{1,}).*$; \
[[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]}
这使我们能够提取任意长度的数字组成的首个连续序列,该序列可能被其他文本/字符包围。
注意:regex=[^0-9]*([0-9]{5,5}).*$;
仅匹配刚好有5个数字组成的序列。 :-)
(1): 比为每个短文本调用外部工具更快。对于大文件内的所有处理来说,不如在sed或awk中进行处理。
echo $name
更改为 echo "$name"
,否则 name=' * 12345 *'
将导致您的输出包含文件名中的数字。 - Charles Duffy根据要求:
我有一个由x个字符组成的文件名,然后是一个由单个下划线包围的五位数字序列,再然后是另一组由x个字符组成的字符。我想取出这个5位数字并将其放入变量中。
我找到了一些可能有用的 grep
方法:
$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+"
12345
$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}"
12345
使用-Po
语法:
$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+'
12345
或者,如果你希望它恰好适合5个字符:
$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}'
12345
最后,要将其存储在变量中,只需使用var=$(command)
语法。
abc_12345_def_67890_ghi_def
是一个有效的输入,那么你想要发生什么?假设只有一个5位数字序列,基于你对输入的定义,仍然可以将abc_def_12345_ghi_jkl
、1234567_12345_1234567
或12345d_12345_12345e
视为有效输入。大多数下面的答案都无法处理这种情况。 - gman_
分隔符、输入字段中只包含目标字符串等)的具体答案。而最佳(最通用和最快速)答案在10年后只有7个赞,而其他有限的答案则有数百个赞。这让我失去了对开发者的信心。 - Dan Dascalescu