在Bash中提取子串

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

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

我对实现这一目标的各种不同方式非常感兴趣。

1666521 次浏览

使用削减

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

更通用:

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

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

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:]]')

还有bash构建'exr'命令:

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

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

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

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

如果一个为常量,则以下参数展开执行子字符串提取:

b=${a:12:5}

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

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

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

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

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

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

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

一个非常小的变体也可以在ksh93中工作。

我是这样做的:

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 -c startIndx-stopIndx

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

str='someletters_12345_morele34ters.ext's1="${str#"${str%%[[:digit:]]*}"}"   # strip off non-digit prefix from strs2="${s1%%[^[:digit:]]*}"            # strip off non-digit suffix from s1echo "$s2"                           # 12345

如果有人想要更严格的信息,你也可以像这样在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}Substring Expansion.  Expands to  up  to  length  characters  ofparameter  starting  at  the  character specified by offset.  Iflength is omitted, expands to the substring of parameter  start‐ing at the character specified by offset.  length and offset arearithmetic expressions (see ARITHMETIC  EVALUATION  below).   Ifoffset  evaluates  to a number less than zero, the value is usedas an offset from the end of the value of parameter.  Arithmeticexpressions  starting  with  a - must be separated by whitespacefrom the preceding : to be distinguished from  the  Use  DefaultValues  expansion.   If  length  evaluates to a number less thanzero, and parameter is not @ and not an indexed  or  associativearray,  it is interpreted as an offset from the end of the valueof 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 toone 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  separatedfrom  the  colon  by  at least one space to avoid being confusedwith the :- expansion.  Substring indexing is zero-based  unlessthe  positional  parameters are used, in which case the indexingstarts at 1 by default.  If offset  is  0,  and  the  positionalparameters are used, $0 is prefixed to the list.

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

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

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

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

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

按照要求

我有一个文件名,有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)语法。

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

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

我用它在没有%N表示date的嵌入式系统上获得毫秒级分辨率:

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

如果我们关注以下概念:
“运行(一个或多个)数字”

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

name='someletters_12345_moreleters.ext'
echo $name | sed 's/[^0-9]*//g'    # 12345echo $name | tr -c -d 0-9          # 12345

但是如果$name包含多个数字,上面的操作将失败:

如果"name=someletters_12345_moreleters_323_end.ext",则:

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

我们需要使用常规表达式(regex)。
要仅选择第一次运行(12345而不是323)在se和perl中:

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中进行所有处理更快。

好的,这里使用空字符串进行纯参数替换。需要注意的是,我仅将小报更多定义为字符。如果它们是字母数字,这将无法正常工作。

filename=someletters_12345_moreletters.extsubstring=${filename//@(+([a-z])_|_+([a-z]).*)}echo $substring12345

bash解决方案:

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

这将破坏一个名为x的变量。varx可以更改为var_

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

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

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

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

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

使用我的代码,您可以确切地提到您想要的内容。解释:

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

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

给定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.txtecho $xST

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

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

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


> man sed | grep s/regexp/replacement -A 2s/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  &  torefer 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_s_ubstitute
  • [0-9]+匹配1+位
  • \1链接到正则表达式输出的组n.1(组0是整个匹配,组1是括号内的匹配)
  • p标志_p_rinting

所有转义\都是为了使sed的regexp处理工作。

隐含结束,类似于JS和Java实现。如果您不希望这样,请删除+1。

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

示例:

    substring 01234 001234substring 012345 0012345substring 012345 0 00substring 012345 1 11substring 012345 1 212substring 012345 0 101substring 012345 0 2012substring 012345 0 30123substring 012345 0 401234substring 012345 0 5012345

更多示例调用:

    substring 012345 0012345substring 012345 112345substring 012345 22345substring 012345 3345substring 012345 445substring 012345 55substring 012345 6    
substring 012345 3 5345substring 012345 3 434substring 012345 2 4234substring 012345 1 3123

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

#方法1)使用bash

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

#方法2)使用剪切

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

#方法3)使用awk时

 str=2020-08-08T07:40:00.000Zawk '{time=gensub(/.{11}(.{8}).*/,"\\1","g",$1); print time}' <<< $str

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

代码:

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

输出:

12345

这是substring.sh档案

用法

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

substring.sh沿着这条线

#echo "starting substring"chars=$1start=$(($2))end=$3
i=0o=""if [[ -z $end ]]; thenend=`echo "$chars " | wc -c`elseend=$((end))fi#echo "length is " $ea=`echo $chars | sed  's/\(.\)/\1 /g'`#echo "a is " $afor c in $ado#echo "substring" $i $e $cif [[ i -lt $start ]]; then: # DO Nothingelif [[ i -gt $end ]]; thenbreak;elseo="$o$c"fii=$(($i+1))done#echo substring returning $oecho $o

一个简单的方法来使用Sed替换:

result=$(echo "someletters_12345_moreleters.ext" | sed 's/.*_\(.*\)_.*/\1/g')echo $result