Extract substring in Bash
给定一个格式为
为了强调这一点,我有一个文件名,文件名有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} |
如果文件名的格式始终为
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重置为之前的值,或者在之后重置
遵循要求
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 |
然后使用
1 2 | $ echo"someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 12345 |
或者,如果要使其正好适合5个字符:
1 2 | $ echo"someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 12345 |
最后,为了使其存储在变量中,只需要使用
没有任何子流程,您可以:
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]} |
这允许我们提取任意长度的第一行数字由任何其他文本/字符包围。
注:
(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]} |
注意:上面是一个正则表达式,并且仅限于由下划线包围的五位数的特定方案。如果需要不同的匹配,请更改正则表达式。
我喜欢
1 2 3 4 | > var="someletters_12345_moreletters.ext" > digits=$( echo $var | sed"s/.*_\([0-9]\+\).*/\1/p" -n ) > echo $digits 12345 |
一个更一般的选择是不要假定您有一个下划线
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 标志用于打印
所有逃逸的
给定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 |
我的答案将对你想要的字符串有更多的控制。下面是有关如何从字符串中提取
1 2 3 4 | str="someletters_12345_moreleters.ext" str=${str#*_} str=${str%_more*} echo $str |
如果要提取具有任何字符(如
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' |
这将删除名为
1 2 | input='someletters_12345_moreleters.ext' IFS="_" read -r _ digs _ <<<"$input" |