【问题标题】:How to extract a substring using bash如何使用 bash 提取子字符串
【发布时间】:2014-05-15 18:40:02
【问题描述】:

我有一个代表日期的字符串,如下所示:

    "May 5 2014"

我想知道如何从中提取“5”。

到目前为止我的尝试:

   echo "May 5 2014" | sed 's/[^0-9]*\s//'

返回“5 2014”

很抱歉提出补救问题。刚接触 bash。

【问题讨论】:

标签: bash


【解决方案1】:

使用cut:

echo "May 5 2014" | cut -d' ' -f2

awk:

echo "May 5 2014" | awk '{print $2}'

如果您想在没有外部实用程序的情况下使用它,这将是一个两步过程:

s="May 5 2014"
t="${s#* }"
echo "${t% *}"

【讨论】:

  • devnull,我的日期字符串存储在一个名为 $a 的变量中。当我执行“newvar = $(echo $a|cut -d' ' -f2)”时,我收到一条错误消息,提示找不到 newvar
  • @dot 消除 = 周围的空格。
【解决方案2】:

使用 sed,一种可能性是:

echo "May 5 2014" | sed 's/.* \([0-9]*\) .*/\1/'

另一个

echo "May 5 2014" | sed 's/[^ ]* //;s/ [^ ]*//'

另一个

echo "May 5 2014" | sed 's/\(.*\) \(.*\) \(.*\)/\2/'

使用 grep

echo "May 5 2014" | grep -oP '\b\d{1,2}\b'

或perl

echo "May 5 2014" | perl -lanE 'say $F[1]'

作为好奇心

echo "May 5 2014" | xargs -n1 | head -2 | tail -1
echo "May 5 2014" | xargs -n1 | sed -n 2p
echo "May 5 2014" | xargs -n1 | egrep '^[0-9]{1,2}$'

最后是纯 bash 解决方案,无需启动任何外部命令

aaa="May 5 2014"
[[ $aaa =~ (.*)[[:space:]](.*)[[:space:]](.*) ]] && echo ${BASH_REMATCH[2]}

aaa="May 5 2014"
re="(.*) (.*) (.*)"
[[ $aaa =~ $re ]] && echo ${BASH_REMATCH[2]}

编辑

因为 Keith Reynolds 要求一些基准测试,所以我测试了以下脚本。使用 time 并不是完美的基准测试工具,但可以提供一些洞察力。

  • 每个测试输出 N 倍的结果(以 wc 计)
  • 注意,外部命令仅执行 10_000 次,而纯 bash 解决方案为 100_000 次

这是脚本:

xbench_with_read() {
    let i=$1; while ((i--)); do
        read _ day _ <<< 'May 5 2014'
        echo $day
    done
}

xbench_regex_3x_assign() {
    let i=$1; while ((i--)); do
        aaa="May 5 2014"
        re="(.*) (.*) (.*)"
        [[ $aaa =~ $re ]] && month="${BASH_REMATCH[1]}" && day="${BASH_REMATCH[2]}" && year="${BASH_REMATCH[3]}" && echo "$day"
    done
}

xbench_regex_1x_assign() {
    let i=$1; while ((i--)); do
        aaa="May 5 2014"
        re="(.*) (.*) (.*)"
        [[ $aaa =~ $re ]] && day=${BASH_REMATCH[2]} && echo "$day"
    done
}

xbench_var_expansion() {
    let i=$1; while ((i--)); do
        s="May 5 2014"
        t="${s#* }"
        echo "${t% *}"
    done
}

xbench_ext_cut() {
    let i=$1; while ((i--)); do
        echo "May 5 2014" | cut -d' ' -f2
    done
}

xbench_ext_grep() {
    let i=$1; while ((i--)); do
        echo "May 5 2014" | grep -oP '\b\d{1,2}\b'
    done
}

xbench_ext_sed() {
    let i=$1; while ((i--)); do
        echo "May 5 2014" | sed 's/\(.*\) \(.*\) \(.*\)/\2/'
    done
}

xbench_ext_xargs() {
    let i=$1; while ((i--)); do
        echo "May 5 2014" | xargs -n1 | sed -n 2p
    done
}

title() {
    echo '~ -'$___{1..20} '~' >&2
    echo "Timing $1 $2 times" >&2
}

for script in $(compgen -A function | grep xbench)
do
    cnt=100000
    #external programs run 10x less times
    [[ $script =~ _ext_ ]] && cnt=$(( $cnt / 10 ))
    title $script $cnt
    time $script $cnt | wc -l
done

这里是原始结果:

~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_ext_cut 10000 times
   10000

real    0m37.752s
user    0m14.587s
sys 0m25.723s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_ext_grep 10000 times
   10000

real    1m35.570s
user    0m21.778s
sys 0m34.524s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_ext_sed 10000 times
   10000

real    0m41.628s
user    0m15.310s
sys 0m26.422s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_ext_xargs 10000 times
   10000

real    1m42.235s
user    0m46.601s
sys 1m11.238s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_regex_1x_assign 100000 times
  100000

real    0m11.215s
user    0m8.784s
sys 0m0.907s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_regex_3x_assign 100000 times
  100000

real    0m14.669s
user    0m12.419s
sys 0m1.027s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_var_expansion 100000 times
  100000

real    0m5.148s
user    0m4.658s
sys 0m0.788s
~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~ - ~
Timing xbench_with_read 100000 times
  100000

real    0m27.700s
user    0m6.279s
sys 0m19.724s

按实际执行时间排序

纯 bash 解决方案 100_000 次

  1. xbench_var_expansion - 实际 0m5.148s - 5.2
  2. xbench_regex_1x_assign - 真正的 0m11.215s - 11.2
  3. xbench_regex_3x_assign - 真正的 0m14.669s - 14.7
  4. xbench_with_read - 真正的 0m27.700s - 27.7

这并不奇怪 - 变量扩展是最快的解决方案。

外部程序仅 10_000 次

  1. xbench_ext_cut - 真正的 0m37.752s - 37.8
  2. xbench_ext_sed - 真正的 0m41.628s - 41.6
  3. xbench_ext_grep - 真正的 1m35.570s - 95.6
  4. xbench_ext_xargs - 实际 1m42.235s - 102.2

这里有两个惊喜(至少对我来说):

  • grep 解决方案是 sed 的 2x slover
  • xargs(好奇心解决方案)只比 grep 慢一点

环境:

$ uname -a
Darwin marvin.local 13.1.0 Darwin Kernel Version 13.1.0: Thu Jan 16 19:40:37 PST 2014; root:xnu-2422.90.20~2/RELEASE_X86_64 x86_64

$ LC_ALL=C bash --version
GNU bash, version 4.2.45(2)-release (i386-apple-darwin13.0.0)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

【讨论】:

  • 我喜欢这里的多样性。我实际上只是自己写了你的第一个可能性,但忘记了 [0-9]* 之前的额外空格*
  • @SS781 - 最好的方法是 cut 已经由 devnull 回答
  • cut 很小 - 启动速度如此之快,并且在脚本中输入的时间很短;)。但实际上最好的是纯 bash 解决方案尚未被任何人展示,因为纯 bash 不会启动任何外部程序...
【解决方案3】:

使用 awk :

echo "May 5 2014" | awk '{print $2}'

【讨论】:

    【解决方案4】:

    如果您正在编写一个需要解析日期字符串的脚本,您当然可以使用 sed 等来完成,而且确实这里已经有几个答案可以很好地解决问题。

    但是,我的建议是让date 程序为您完成繁重的工作:

    $ date -d "May 5 2014" +%-d
    5
    

    date 程序的维护者无疑花费了 许多 小时和数天的时间来正确处理他们的日期解析代码。为什么不利用这项工作而不是自己动手?

    编辑

    添加了 BSD 解决方案,例如适用于 (Mac OS X)

    date -j -f '%b %d %Y' 'May 5 2014' '+%d'
    

    在 BSD 上,需要告诉date -f format 的“传入”日期是什么格式,并以 +format 的格式输出。 -j 的意思是,不要设置日期

    【讨论】:

    • 不错的一个!不幸的是,对于 BSD 系统(OS X)不起作用(需要另一种语法)
    • 请注意,尽管date 不接受您能想到的任何日期格式(例如,它抱怨“2014 年 5 月 5 日”),但它仍然比假设单个日期格式灵活得多格式。例如,date接受诸如“2014 年 5 月 5 日”、“5 月 5 日”、“2014-05-05”、“2014-5-5”等日期。
    • 除此之外做任何事情对于日期解析(以及许多其他问题的重复)来说有点奇怪。问题中没有提到BSD。 +1
    • @BroSlow 更别提Linux了...你不能假设每个人都使用GNU date,这里也有很多Mac用户。无论如何,我同意这个答案——用日期解析日期很好——但需要注意不同的操作系统语法。
    • @jm666 没有反对 bsd(尽管我不喜欢 find、stat 等一些工具的 bsd 变体),gnu 更为普遍,并且 OP 询问 bsd 的问题往往会得到用 osx、solaris、bsd 等标记...但显然很高兴提供多种解决方案。
    【解决方案5】:

    Bash 的内置读取命令可以将输入拆分为多个变量。 '

    read first second remainder <<< "May 5 2014"
    

    之后,“$first”将是“May”,“$second”将是“5”,“$remainder”将是“2014”

    通常的做法是使用“”作为不感兴趣字段的占位符,因为 shell 会自动覆盖 $

    read _ day _ <<< 'May 5 2014 utc'
    

    【讨论】:

    • 这真的很整洁 :) +1
    • @dave sines 不仅简洁,而且read month day year &lt;&lt;&lt; "May 5 2014" 速度快得令人难以置信。我做了一些测试,发现它比 day=$(echo "May 5 2014" | cut -d' ' -f2) 快​​ 20 倍以上。如果要对月日和年做同样的事情,它的速度会快 60 倍以上。谢谢!
    • @KeithReynolds @devnull 的纯 bash 解决方案快 5 倍,而我的纯 bash-regex 解决方案比 read 解决方案快 3 倍。所以,它很整洁——但不是最快的:)
    • @jm666 我还发现,如果您只寻找一天,您的 pure-bash-regex 解决方案比这个读取解决方案快 3 倍。另一方面,read month day year &lt;&lt;&lt; "May 5 2014" 的速度与re="(.*) (.*) (.*)"; [[ $aaa =~ $re ]]; month=${BASH_REMATCH[1]}; day=${BASH_REMATCH[2]};year=${BASH_REMATCH[3]} 差不多
    • @KeithReynolds 在我的系统中 100000 次,读取解决方案:27 秒,正则表达式 3x 分配 10 秒,正则表达式 1x 分配 8 秒,devnulls 解决方案 5.4 秒。 ;) 无论如何,这真的不是很重要——所有纯 bash 解决方案都很好。 ;) :)
    【解决方案6】:

    您可以使用 bash substring expansion 并应用偏移量 (:4) 和长度 (:1) 值。在字符串格式发生变化的情况下,只需调整偏移量和长度值即可。

    这是一个例子:

    $ date_format="May 5 2014"
    $ echo "${date_format:4:1}"
    5
    
    $ date_format="2014 May 5"
    $ echo "${date_format: -1:1}"    # <- Watch that space before the negative value
    5
    
    $ date_format="5 May 2014"
    $ echo "${date_format:0:1}"
    5
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2021-10-31
      • 2015-04-26
      • 2019-06-22
      相关资源
      最近更新 更多