在Bash中提取子字符串

Ber*_*yan 684 string bash shell substring

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

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

我对可以实现的不同方式的数量非常感兴趣.

JB.*_*JB. 1032

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

b=${a:12:5}
Run Code Online (Sandbox Code Playgroud)

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

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

tmp=${a#*_}   # remove prefix ending in "_"
b=${tmp%_*}   # remove suffix starting with "_"
Run Code Online (Sandbox Code Playgroud)

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

所提出的两种解决方案都是纯粹的bash,没有涉及过程产生,因此非常快.

  • [强大的参数扩展](http://wiki.bash-hackers.org/syntax/pe) (42认同)
  • JB,你应该澄清"12"是偏移量(从零开始),"5"是长度.另外,为@gontard的链接+1,将它全部解决! (21认同)
  • @SpencerRathbun`bash:$ {$ {a#*_}%_*}:我的GNU bash 4.2.45上的错误替换. (17认同)
  • 顺便说一句,偏移参数也可以是负数。您只需注意不要将其粘到冒号上,否则 bash 会将其解释为“:-”“使用默认值”替换。因此,`${a: -12:5}` 生成距离末尾 12 个字符的 5 个字符,而 `${a: -12:-5}` 生成 end-12 和 end-5 之间的 7 个字符。 (5认同)
  • @jonnyB,过去的某个时间有效.我的同事告诉我它停了下来,他们把它变成了一个sed命令或什么的.在历史中看它,我在一个`sh`脚本中运行它,这可能是破折号.在这一点上,我不能让它继续工作. (2认同)
  • 当在脚本中以“sh run.sh”的形式运行此命令时,可能会出现错误替换错误。为了避免这种情况,请更改 run.sh 的权限 (chmod +x run.sh),然后以“./run.sh”运行脚本 (2认同)

Fer*_*anB 651

使用剪切:

echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2
Run Code Online (Sandbox Code Playgroud)

更通用的:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
Run Code Online (Sandbox Code Playgroud)

  • -f标志采用基于1的索引,而不是程序员习惯使用的基于0的索引. (66认同)
  • 如何修改代码以获取最后一个'_'? (10认同)
  • '-f'后面的数字'2'是告诉shell提取第二组子字符串。 (4认同)
  • 更通用的答案正是我想要的,谢谢 (3认同)
  • 你应该在`echo`的参数周围使用双引号,除非你确定变量不能包含不规则的空格或shell元字符.另见http://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-variable (3认同)
  • INPUT=someletters_12345_moreleters.ext SUBSTRING=$(echo $INPUT| cut -d'_' -f 2) echo $SUBSTRING (2认同)
  • 也许这是一个愚蠢的问题,但为什么不`SUBSTRING=$INPUT | cut -d'_' -f 2` 工作吗? (2认同)
  • @Neil `|` 用于将输入通过管道传输到 `cut` 命令中。正在通过管道传输的是来自“$INPUT”*命令*的*输出*(我们在这里将“$INPUT”视为*字符串*)。因此,我们回显“$INPUT”的内容,这会产生要通过管道传输的输出。(如有错误请见谅,但这是我的理解) (2认同)

Joh*_*itb 91

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

number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1)
Run Code Online (Sandbox Code Playgroud)

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

number=${filename:offset:length}
Run Code Online (Sandbox Code Playgroud)

如果您的文件名始终具有stuff_digits_...您可以使用awk 的格式:

number=$(echo $filename | awk -F _ '{ print $2 }')
Run Code Online (Sandbox Code Playgroud)

除了数字之外,还有另一种解决方案,使用

number=$(echo $filename | tr -cd '[[:digit:]]')
Run Code Online (Sandbox Code Playgroud)

  • 如果我想从文件的最后一行提取数字/单词怎么办? (2认同)

bro*_*179 86

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

  • `开始= 5;停止= 9; 回应"西班牙的雨"| cut -c $ start - $(($ stop-1))` (3认同)
  • 有没有像startIndex-lastIndex - 1这样的东西? (2认同)
  • @Niklas 在 bash 中,proly `startIndx-$((lastIndx-1))` (2认同)
  • `cut -c 9 - $((lastIndx-1))`不起作用. (2认同)
  • 问题是输入是动态的,因为我也使用管道来获取它,所以它基本上是动态的。`git log --oneline | git log --oneline | git log --oneline | git log --oneline 头-1 | 切-c 9-(结束-1)` (2认同)

jpe*_*lli 33

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

$ man bash [press return key]
/substring  [press return key]
[press "n" key]
[press "n" key]
[press "n" key]
[press "n" key]
Run Code Online (Sandbox Code Playgroud)

结果:

${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.

  • 上面提到的一个非常重要的负值警告:**以 - 开头的算术表达式必须与前面的 : 用空格分隔,以与使用默认值扩展区分开来。** 所以要获得 var 的最后四个字符: `${var: -4}` (3认同)

PEZ*_*PEZ 20

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

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
Run Code Online (Sandbox Code Playgroud)

  • 当你有一些复杂的东西时,正则表达式是真正的交易,简单地计算下划线不会"削减"它. (12认同)

use*_*062 19

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

a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345
Run Code Online (Sandbox Code Playgroud)

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

  • +1您可以用另一种方式编写,以避免取消设置`IFS`和位置参数:`IFS = _ read -r _ digs _ <<<"$ a"; 回声"$ digs"` (4认同)
  • 这取决于路径名扩展!(所以它坏了). (2认同)

nic*_*bot 15

这是我怎么做的:

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

注意:以上是正则表达式,仅限于由下划线包围的五位数的特定场景.如果需要不同的匹配,请更改正则表达式.

  • 即使您需要提取多个内容,这也是一种通用方法,就像我所做的那样。 (3认同)
  • 确实,这是最通用的答案,应该接受。它适用于正则表达式,而不仅仅是固定位置的字符串或相同定界符(启用`cut`)之间的字符串。它还不依赖于执行外部命令。 (2认同)
  • @Paul我在我的答案中添加了更多细节。希望有帮助。 (2认同)

fed*_*qui 12

遵循要求

我有一个带有x个字符的文件名,然后是一个五位数序列,两边都是一个下划线,然后是另一组x个字符.我想取5位数字并将其放入变量中.

我发现了一些grep可能有用的方法:

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+" 
12345
Run Code Online (Sandbox Code Playgroud)

或更好

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}" 
12345
Run Code Online (Sandbox Code Playgroud)

然后用-Po语法:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 
12345
Run Code Online (Sandbox Code Playgroud)

或者如果你想让它恰好适合5个字符:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 
12345
Run Code Online (Sandbox Code Playgroud)

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

  • 我相信现在没有必要使用egrep,命令本身会警告你:`'asrep'的调用已被弃用; 使用'grep -E'代替`.我已经编辑了你的答案. (2认同)

Dar*_*ron 10

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

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

一个非常小的变体也适用于ksh93.


小智 10

如果我们专注于以下概念:
"一个(一个或几个)数字的运行"

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

name='someletters_12345_moreleters.ext'

echo $name | sed 's/[^0-9]*//g'    # 12345
echo $name | tr -c -d 0-9          # 12345
Run Code Online (Sandbox Code Playgroud)

但如果$ name包含多个数字,则上述操作将失败:

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

echo $name | sed 's/[^0-9]*//g'    # 12345323
echo $name | tr -c -d 0-9          # 12345323
Run Code Online (Sandbox Code Playgroud)

我们需要使用常规表达式(正则表达式).
要在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";'
Run Code Online (Sandbox Code Playgroud)

但我们也可以直接在bash (1)中做到:

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

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

注意:regex=[^0-9]*([0-9]{5,5}).*$;仅匹配5位数运行.:-)

(1):比为每个短文本调用外部工具更快.对于在大型文件中执行sed或awk内的所有处理并不快.


小智 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
Run Code Online (Sandbox Code Playgroud)


小智 9

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

#方法1) 使用bash

 str=2020-08-08T07:40:00.000Z
 echo ${str:11:8}
Run Code Online (Sandbox Code Playgroud)

#方法2)使用剪切

 str=2020-08-08T07:40:00.000Z
 cut -c12-19 <<< $str
Run Code Online (Sandbox Code Playgroud)

#method3) 使用 awk 时

 str=2020-08-08T07:40:00.000Z
 awk '{time=gensub(/.{11}(.{8}).*/,"\\1","g",$1); print time}' <<< $str
Run Code Online (Sandbox Code Playgroud)


Ale*_*thy 7

我的答案将更好地控制您想要从字符串中获得的内容。这是有关如何12345从字符串中提取的代码

str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str
Run Code Online (Sandbox Code Playgroud)

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

str="someletters_123-45-24a&13b-1_moreleters.ext"
Run Code Online (Sandbox Code Playgroud)

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

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

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


Cam*_*mpa 7

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

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

稍微更通用的选择是不要假设你有一个下划线_标记数字序列的开头,因此例如剥离你在序列之前得到的所有非数字: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.
Run Code Online (Sandbox Code Playgroud)

更多相关信息,如果你对regexp不太自信:

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

所有逃脱\都是为了进行正则sed表达式处理工作.


小智 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
Run Code Online (Sandbox Code Playgroud)


erw*_*win 5

许多过时的解决方案都需要管道和子壳来解决这个问题。从bash版本3(2004年发布)开始,它有一个内置的正则表达式比较运算符=~

input="someletters_12345_moreleters.ext"
# match: underscore followed by 1 or more digits followed by underscore
[[ $input =~ _([0-9]+)_ ]]
echo ${BASH_REMATCH[1]}
Run Code Online (Sandbox Code Playgroud)

输出:

12345
Run Code Online (Sandbox Code Playgroud)

请注意,如果您不太精通编写正则表达式,我建议您阅读掌握正则表达式

在此输入图像描述

如果您只需要弄清楚如何让 RegExp 工作,并且它与您的想法不符,请尝试 RegEx101.com 上的在线 GUI 并将“Flavor”设置为“PCRE”,以便获得 POSIX 风格的字符类就像[[:digit:]]那样bash使用。

在此输入图像描述


归档时间:

查看次数:

1142601 次

最近记录:

6 年,2 月 前