902

给定表单中的文件名someletters_12345_moreleters.ext,我想提取 5 位数字并将它们放入变量中。

因此,为了强调这一点,我有一个包含 x 个字符的文件名,然后是一个五位数字序列,两边各有一个下划线,然后是另一组 x 个字符。我想取 5 位数字并将其放入变量中。

我对可以实现这一目标的不同方式的数量非常感兴趣。

4

24 回答 24

1324

您可以使用参数扩展来执行此操作。

如果a是常量,则以下参数扩展执行子字符串提取:

b=${a:12:5}

其中12是偏移量(从零开始),5是长度

如果数字周围的下划线是输入中唯一的下划线,则可以分两步去除前缀和后缀(分别):

tmp=${a#*_}   # remove prefix ending in "_"
b=${tmp%_*}   # remove suffix starting with "_"

如果还有其他下划线,无论如何它可能是可行的,尽管更棘手。如果有人知道如何在一个表达式中执行两个扩展,我也想知道。

提出的两种解决方案都是纯 bash,不涉及进程生成,因此速度非常快。

于 2009-01-09T15:52:35.563 回答
854

使用剪切

echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2

更通用:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
于 2009-01-09T13:56:14.113 回答
111

通用解决方案,其中数字可以在文件名中的任何位置,使用第一个这样的序列:

number=$(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:]]')
于 2009-01-09T14:00:08.723 回答
109

只是尝试使用cut -c startIndx-stopIndx

于 2010-09-22T17:54:15.797 回答
44

这是我的做法:

FN=someletters_12345_moreleters.ext
[[ ${FN} =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}

解释:

特定于 Bash 的:

正则表达式 (RE):_([[:digit:]]{5})_

  • _是用于为被匹配的字符串划分/锚定匹配边界的文字
  • ()创建捕获组
  • [[:digit:]]是一个角色类,我认为它不言自明
  • {5}表示前一个字符、类(如本例中)或组中的五个必须匹配

在英语中,你可以认为它的行为是这样的:FN字符串逐个字符地迭代,直到我们看到一个_捕获组被打开并且我们尝试匹配五个数字。如果此时匹配成功,则捕获组保存遍历的五个数字。如果下一个字符是_,则条件成功,捕获组在 中可用BASH_REMATCH,并且NUM=可以执行下一条语句。如果匹配的任何部分失败,保存的详细信息将被处理掉,并在_. 例如,如果FNwhere _1 _12 _123 _1234 _12345_,在找到匹配项之前会有四个错误的开始。

于 2009-01-12T19:43:20.790 回答
37

如果有人想要更严格的信息,你也可以像这样在 man bash 中搜索

$ man bash [press return key]
/substring  [press return key]
[press "n" key]
[press "n" key]
[press "n" key]
[press "n" key]

结果:

${参数:偏移量}
       ${参数:偏移量:长度}
              子串扩展。扩展到最多长度字符
              参数从 offset 指定的字符开始。如果
              length 被省略,展开为参数 start- 的子字符串
              在由 offset 指定的字符处。长度和偏移量是
              算术表达式(见下面的算术评估)。如果
              offset 计算为小于零的数字,使用该值
              作为参数值末尾的偏移量。算术
              以 - 开头的表达式必须用空格分隔
              与前面的:要区别于使用默认值
              价值观扩张。如果长度计算结果小于
              零,并且参数不是@,也不是索引或关联
              数组,它被解释为从值末尾的偏移量
              参数而不是字符数,以及扩展
              sion 是两个偏移量之间的字符。如果参数是
              @,结果是从 off 开始的长度位置参数
              放。如果参数是由@ 或下标的索引数组名称
              *,结果是以数组开头的长度成员
              ${参数[偏移]}。相对于
              比指定数组的最大索引大一。子
              应用于关联数组的字符串扩展会产生不正确的
              罚款结果。请注意,必须将负偏移量分开
              与冒号相距至少一个空格以避免混淆
              使用 :- 扩展。子字符串索引是从零开始的,除非
              使用位置参数,在这种情况下,索引
              默认从 1 开始。如果偏移量为 0,则位置
              使用参数,$0 是列表的前缀。
于 2013-05-31T15:00:54.763 回答
23

我很惊讶这个纯 bash 解决方案没有出现:

a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345

您可能希望将 IFS 重置为之前或unset IFS之后的值!

于 2013-06-03T17:34:40.723 回答
21

基于 jor 的回答(这对我不起作用):

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
于 2009-01-09T15:41:11.937 回答
13

如果我们关注以下概念:
“一系列(一个或几个)数字”

我们可以使用几个外部工具来提取数字。
我们可以很容易地擦除所有其他字符,无论是 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 中对大文件进行所有处理快。

于 2014-08-05T08:11:19.947 回答
12

遵循要求

我有一个包含 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)语法即可。

于 2013-06-26T12:13:49.147 回答
11

没有任何子流程,您可以:

shopt -s extglob
front=${input%%_+([a-zA-Z]).*}
digits=${front##+([a-zA-Z])_}

一个非常小的变体也可以在 ksh93 中使用。

于 2009-01-09T16:13:38.500 回答
9

这是一个前缀-后缀解决方案(类似于 JB 和 Darron 给出的解决方案),它匹配第一个数字块并且不依赖于周围的下划线:

str='someletters_12345_morele34ters.ext'
s1="${str#"${str%%[[:digit:]]*}"}"   # strip off non-digit prefix from str
s2="${s1%%[^[:digit:]]*}"            # strip off non-digit suffix from s1
echo "$s2"                           # 12345
于 2011-05-06T12:50:13.293 回答
9

我的回答将更好地控制你想要从你的字符串中得到什么。这是有关如何12345从字符串中提取的代码

str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str

abc如果您想提取具有任何字符或任何特殊字符(如_or )的内容,这将更有效-。例如:如果您的字符串是这样的,并且您想要之后someletters_和之前的所有内容_moreleters.ext

str="someletters_123-45-24a&13b-1_moreleters.ext"

使用我的代码,您可以提及您到底想要什么。解释:

#*它将删除前面的字符串,包括匹配的键。这里我们提到的键是_ %它将删除以下字符串,包括匹配的键。这里我们提到的关键是'_more*'

自己做一些实验,你会发现这很有趣。

于 2016-07-29T07:41:26.123 回答
7

我喜欢sed处理正则表达式组的能力:

> var="someletters_12345_moreletters.ext"
> digits=$( echo "$var" | sed "s/.*_\([0-9]\+\).*/\1/p" -n )
> echo $digits
12345

一个稍微更通用的选择是不要假设您有一个下划线_标记您的数字序列的开始,因此例如剥离您在序列之前获得的所有非数字:s/[^0-9]\+\([0-9]\+\).*/\1/p


> man sed | grep s/regexp/replacement -A 2
s/regexp/replacement/
    Attempt to match regexp against the pattern space.  If successful, replace that portion matched with replacement.  The replacement may contain the special  character  &  to
    refer to that portion of the pattern space which matched, and the special escapes \1 through \9 to refer to the corresponding matching sub-expressions in the regexp.

更多关于这一点,如果你对正则表达式不太自信:

  • s是为_s_substitute
  • [0-9]+匹配 1+ 位数
  • \1链接到正则表达式输出的组 n.1(组 0 是整个匹配,组 1 是在这种情况下括号内的匹配)
  • p标志用于 _p_rinting

所有的转义\都是为了使sed' 的正则表达式处理工作。

于 2016-10-21T08:12:04.813 回答
6

给定 test.txt 是一个包含“ABCDEFGHIJKLMNOPQRSTUVWXYZ”的文件

cut -b19-20 test.txt > test1.txt # This will extract chars 19 & 20 "ST" 
while read -r; do;
> x=$REPLY
> done < test1.txt
echo $x
ST
于 2016-08-14T19:44:45.643 回答
6

shell cut - 从字符串中打印特定范围的字符或给定部分

#method1) 使用 bash

 str=2020-08-08T07:40:00.000Z
 echo ${str:11:8}

#method2) 使用剪切

 str=2020-08-08T07:40:00.000Z
 cut -c12-19 <<< $str

#method3) 使用 awk 时

 str=2020-08-08T07:40:00.000Z
 awk '{time=gensub(/.{11}(.{8}).*/,"\\1","g",$1); print time}' <<< $str
于 2020-08-08T09:08:11.973 回答
4

类似于 php 中的 substr('abcdefg', 2-1, 3):

echo 'abcdefg'|tail -c +2|head -c 3
于 2013-06-26T11:34:08.277 回答
3

好的,这里是带有空字符串的纯参数替换。需要注意的是,我已将somelettersmoreletters定义为仅字符。如果它们是字母数字,这将无法正常工作。

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
于 2015-10-26T12:22:56.130 回答
1

还有内置的 bash 'expr' 命令:

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
于 2009-01-09T15:01:02.517 回答
1

一个bash解决方案:

IFS="_" read -r x digs x <<<'someletters_12345_moreleters.ext'

这将破坏一个名为x. varx可以更改为 var _

input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"
于 2016-01-22T05:45:24.207 回答
1

Inklusive end,类似于 JS 和 Java 的实现。如果您不希望这样做,请删除 +1。

function substring() {
    local str="$1" start="${2}" end="${3}"
    
    if [[ "$start" == "" ]]; then start="0"; fi
    if [[ "$end"   == "" ]]; then end="${#str}"; fi
    
    local length="((${end}-${start}+1))"
    
    echo "${str:${start}:${length}}"
} 

例子:

    substring 01234 0
    01234
    substring 012345 0
    012345
    substring 012345 0 0
    0
    substring 012345 1 1
    1
    substring 012345 1 2
    12
    substring 012345 0 1
    01
    substring 012345 0 2
    012
    substring 012345 0 3
    0123
    substring 012345 0 4
    01234
    substring 012345 0 5
    012345

更多示例调用:

    substring 012345 0
    012345
    substring 012345 1
    12345
    substring 012345 2
    2345
    substring 012345 3
    345
    substring 012345 4
    45
    substring 012345 5
    5
    substring 012345 6
    
    substring 012345 3 5
    345
    substring 012345 3 4
    34
    substring 012345 2 4
    234
    substring 012345 1 3
    123
于 2019-12-01T13:53:21.680 回答
1

可能这可以帮助您获得所需的输出

代码 :

your_number=$(echo "someletters_12345_moreleters.ext" | grep -E -o '[0-9]{5}')
echo $your_number

输出 :

12345
于 2021-10-10T16:04:32.830 回答
0

有点晚了,但我刚刚遇到了这个问题,发现了以下内容:

host:/tmp$ asd=someletters_12345_moreleters.ext 
host:/tmp$ echo `expr $asd : '.*_\(.*\)_'`
12345
host:/tmp$ 

我用它在没有 %N 日期的嵌入式系统上获得毫秒分辨率:

set `grep "now at" /proc/timer_list`
nano=$3
fraction=`expr $nano : '.*\(...\)......'`
$debug nano is $nano, fraction is $fraction
于 2013-08-01T08:12:33.863 回答
0

这是一个 substring.sh 文件

用法

`substring.sh $TEXT 2 3` # characters 2-3

`substring.sh $TEXT 2` # characters 2 and after 

substring.sh 遵循这一行

#echo "starting substring"
chars=$1
start=$(($2))
end=$3

i=0
o=""
if [[ -z $end ]]; then
  end=`echo "$chars " | wc -c`
else
  end=$((end))
fi
#echo "length is " $e
a=`echo $chars | sed  's/\(.\)/\1 /g'`
#echo "a is " $a
for c in $a
do
  #echo "substring" $i $e $c
  if [[ i -lt $start ]]; then
    : # DO Nothing
  elif [[ i -gt $end ]]; then
    break;
  else
    o="$o$c"
  fi
  i=$(($i+1))
done
#echo substring returning $o
echo $o
于 2021-11-18T20:15:17.070 回答