【发布时间】:2014-05-15 18:40:02
【问题描述】:
我有一个代表日期的字符串,如下所示:
"May 5 2014"
我想知道如何从中提取“5”。
到目前为止我的尝试:
echo "May 5 2014" | sed 's/[^0-9]*\s//'
返回“5 2014”
很抱歉提出补救问题。刚接触 bash。
【问题讨论】:
标签: bash
我有一个代表日期的字符串,如下所示:
"May 5 2014"
我想知道如何从中提取“5”。
到目前为止我的尝试:
echo "May 5 2014" | sed 's/[^0-9]*\s//'
返回“5 2014”
很抱歉提出补救问题。刚接触 bash。
【问题讨论】:
标签: bash
使用cut:
echo "May 5 2014" | cut -d' ' -f2
或awk:
echo "May 5 2014" | awk '{print $2}'
如果您想在没有外部实用程序的情况下使用它,这将是一个两步过程:
s="May 5 2014"
t="${s#* }"
echo "${t% *}"
【讨论】:
= 周围的空格。
使用 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 并不是完美的基准测试工具,但可以提供一些洞察力。
这是脚本:
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 次
这并不奇怪 - 变量扩展是最快的解决方案。
外部程序仅 10_000 次
这里有两个惊喜(至少对我来说):
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>
【讨论】:
cut 已经由 devnull 回答
cut 很小 - 启动速度如此之快,并且在脚本中输入的时间很短;)。但实际上最好的是纯 bash 解决方案尚未被任何人展示,因为纯 bash 不会启动任何外部程序...
使用 awk :
echo "May 5 2014" | awk '{print $2}'
【讨论】:
如果您正在编写一个需要解析日期字符串的脚本,您当然可以使用 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 的意思是,不要设置日期。
【讨论】:
date 不接受您能想到的任何日期格式(例如,它抱怨“2014 年 5 月 5 日”),但它仍然比假设单个日期格式灵活得多格式。例如,date将接受诸如“2014 年 5 月 5 日”、“5 月 5 日”、“2014-05-05”、“2014-5-5”等日期。
date,这里也有很多Mac用户。无论如何,我同意这个答案——用日期解析日期很好——但需要注意不同的操作系统语法。
Bash 的内置读取命令可以将输入拆分为多个变量。 '
read first second remainder <<< "May 5 2014"
之后,“$first”将是“May”,“$second”将是“5”,“$remainder”将是“2014”
通常的做法是使用“”作为不感兴趣字段的占位符,因为 shell 会自动覆盖 $。
read _ day _ <<< 'May 5 2014 utc'
【讨论】:
read month day year <<< "May 5 2014" 速度快得令人难以置信。我做了一些测试,发现它比 day=$(echo "May 5 2014" | cut -d' ' -f2) 快 20 倍以上。如果要对月日和年做同样的事情,它的速度会快 60 倍以上。谢谢!
read 解决方案快 3 倍。所以,它很整洁——但不是最快的:)
read month day year <<< "May 5 2014" 的速度与re="(.*) (.*) (.*)"; [[ $aaa =~ $re ]]; month=${BASH_REMATCH[1]}; day=${BASH_REMATCH[2]};year=${BASH_REMATCH[3]} 差不多
您可以使用 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
【讨论】: