关于字符串:提取bash中的子字符串

Extract substring in Bash

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

为了强调这一点,我有一个文件名,文件名有x个字符,然后是一个五位数的序列,每边都有一个下划线,然后是另一组x个字符。我想把5位数的数字放入变量。

我对实现这一目标的各种方法的数量非常感兴趣。


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

1
b=${a:12:5}

其中12是偏移量(以零为基础),5是长度

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

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

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

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


使用切割:

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

更通用:

1
2
3
INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING


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

1
number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1)

另一个精确提取变量一部分的解决方案:

1
number=${filename:offset:length}

如果文件名的格式始终为stuff_digits_...,则可以使用awk:

1
number=$(echo $filename | awk -F _ '{ print $2 }')

另一个解决方案是删除除数字以外的所有内容,使用

1
number=$(echo $filename | tr -cd '[[:digit:]]')


试着用EDOCX1[1]


In case someone wants more rigorous information, you can also search it in man bash like this

1
2
3
4
5
6
$ man bash [press return key]
/substring  [press return key]
[press"n" key]
[press"n" key]
[press"n" key]
[press"n" key]

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
${parameter:offset}
       ${parameter:offset:length}
              Substring Expansion.  Expands to  up  to  length  characters  of
              parameter  starting  at  the  character specified by offset.  If
              length is omitted, expands to the substring of parameter  start‐
              ing at the character specified by offset.  length and offset are
              arithmetic expressions (see ARITHMETIC  EVALUATION  below).   If
              offset  evaluates  to a number less than zero, the value is used
              as an offset from the end of the value of parameter.  Arithmetic
              expressions  starting  with  a - must be separated by whitespace
              from the preceding : to be distinguished from  the  Use  Default
              Values  expansion.   If  length  evaluates to a number less than
              zero, and parameter is not @ and not an indexed  or  associative
              array,  it is interpreted as an offset from the end of the value
              of parameter rather than a number of characters, and the  expan‐
              sion is the characters between the two offsets.  If parameter is
              @, the result is length positional parameters beginning at  off‐
              set.   If parameter is an indexed array name subscripted by @ or
              *, the result is the length members of the array beginning  with
              ${parameter[offset]}.   A  negative  offset is taken relative to
              one greater than the maximum index of the specified array.  Sub‐
              string  expansion applied to an associative array produces unde‐
              fined results.  Note that a negative offset  must  be  separated
              from  the  colon  by  at least one space to avoid being confused
              with the :- expansion.  Substring indexing is zero-based  unless
              the  positional  parameters are used, in which case the indexing
              starts at 1 by default.  If offset  is  0,  and  the  positional
              parameters are used, $0 is prefixed to the list.


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

1
substring=$(expr"$filename" : '.*_\([^_]*\)_.*')


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

1
2
3
4
5
a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345

您可能希望将ifs重置为之前的值,或者在之后重置unset IFS


遵循要求

I have a filename with x number of characters then a five digit
sequence surrounded by a single underscore on either side then another
set of x number of characters. I want to take the 5 digit number and
put that into a variable.

我找到了一些可能有用的方法:

1
2
$ echo"someletters_12345_moreleters.ext" | grep -Eo"[[:digit:]]+"
12345

或更好

1
2
$ echo"someletters_12345_moreleters.ext" | grep -Eo"[[:digit:]]{5}"
12345

然后使用-Po语法:

1
2
$ echo"someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+'
12345

或者,如果要使其正好适合5个字符:

1
2
$ echo"someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}'
12345

最后,为了使其存储在变量中,只需要使用var=$(command)语法。


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

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

一个非常小的变种也可以在KSH93中使用。


如果我们关注以下概念:"一组(一个或多个)数字"

我们可以使用几个外部工具来提取数字。我们可以很容易地删除所有其他字符,无论是sed还是tr:

1
2
3
4
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",则:

1
2
echo $name | sed 's/[^0-9]*//g'    # 12345323
echo $name | tr -c -d 0-9          # 12345323

我们需要使用正则表达式(regex)。要仅选择SED和Perl中的第一次运行(12345而不是323),请执行以下操作:

1
2
3
echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/'
perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print"$num
";'

但我们也可以直接在bash(1)中完成它:

1
2
regex=[^0-9]*([0-9]{1,}).*$; \
[[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]}

这允许我们提取任意长度的第一行数字由任何其他文本/字符包围。

注:regex=[^0-9]*([0-9]{5,5}).*$;将只匹配5位数字。-)

(1):比为每个短文本调用外部工具更快。不比在SED或AWK中处理大型文件更快。


这里有一个前缀后缀解决方案(类似于jb和darron给出的解决方案),它与第一个数字块匹配,不依赖于周围的下划线:

1
2
3
4
str='someletters_12345_morele34ters.ext'
s1="${str#"${str%%[[:digit:]]*}
<hr><P>我会这样做:</P>[cc lang="
bash"]FN=someletters_12345_moreleters.ext
[[ $FN =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}

注意:上面是一个正则表达式,并且仅限于由下划线包围的五位数的特定方案。如果需要不同的匹配,请更改正则表达式。


我喜欢sed与Regex集团打交道的能力:

1
2
3
4
> 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

1
2
3
4
> 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.

更多信息,以防您对regexps不太自信:

  • s是用来代替
  • [0-9]+与1+位匹配
  • \1链接到regex输出的组n.1(组0是整个匹配,组1是括号内的匹配)
  • p标志用于打印

所有逃逸的\都在那里使sed的regexp处理工作。


给定test.txt文件包含"abcdefghijklmnopqrstuvwxyz"

1
2
3
4
5
6
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


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

1
echo 'abcdefg'|tail -c +2|head -c 3

我的答案将对你想要的字符串有更多的控制。下面是有关如何从字符串中提取12345的代码

1
2
3
4
str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str

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

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

有了我的密码,你就能说出你到底想要什么。说明:

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

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


这里是纯参数替换的空字符串。注意,我已经将一些字母和更多字母定义为唯一的字符。如果它们是字母数字,则不会按原样工作。

1
2
3
4
filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345


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

1
2
3
INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match"$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING


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

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

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

1
2
3
4
set `grep"now at" /proc/timer_list`
nano=$3
fraction=`expr $nano : '.*\(...\)......'`
$debug nano is $nano, fraction is $fraction


bash解决方案:

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

这将删除名为x的变量。var x可改为var _

1
2
input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"