147

I'm trying to extract the time from a string using bash, and I'm having a hard time figuring it out.

My string is like this:

US/Central - 10:26 PM (CST)

And I want to extract the 10:26 part.

Anybody knows of a way of doing this only with bash - without using sed, awk, etc?

Like, in PHP I would use - not the best way, but it works - something like:

preg_match( ""(\d{2}\:\d{2}) PM \(CST\)"", "US/Central - 10:26 PM (CST)", $matches );

Thanks for any help, even if the answer uses sed or awk

andrux
  • 2,782
  • 3
  • 22
  • 31

6 Answers6

294

Using pure :

$ cat file.txt
US/Central - 10:26 PM (CST)
$ while read a b time x; do [[ $b == - ]] && echo $time; done < file.txt

another solution with bash regex :

$ [[ "US/Central - 10:26 PM (CST)" =~ -[[:space:]]*([0-9]{2}:[0-9]{2}) ]] &&
    echo ${BASH_REMATCH[1]}

another solution using grep and look-around advanced regex :

$ echo "US/Central - 10:26 PM (CST)" | grep -oP "\-\s+\K\d{2}:\d{2}"

another solution using sed :

$ echo "US/Central - 10:26 PM (CST)" |
    sed 's/.*\- *\([0-9]\{2\}:[0-9]\{2\}\).*/\1/'

another solution using perl :

$ echo "US/Central - 10:26 PM (CST)" |
    perl -lne 'print $& if /\-\s+\K\d{2}:\d{2}/'

and last one using awk :

$ echo "US/Central - 10:26 PM (CST)" |
    awk '{for (i=0; i<=NF; i++){if ($i == "-"){print $(i+1);exit}}}'
Yangshun Tay
  • 49,270
  • 33
  • 114
  • 141
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • Cool! Any chance I use also the hyphen "-" in the pattern? because that grep returns some matches, and I'm only interested in the one that has the hyphen and then a space and then the time..... – andrux Nov 14 '12 at 04:59
  • I could've probably got the perl solution, but it's an excellent plus. Thanks! – andrux Nov 14 '12 at 05:26
  • 1
    Thank you for let me know the \K "trick". grep with perl syntax is really powerful. – Marco Sulla Oct 08 '14 at 12:31
  • 2
    I like the `sed` version but wanted to warn others that `sed` doesn't necessarily take `+` modifier. One way to work around is to use `{1, }` modifier to match one or more. – CodeBrew Jan 27 '20 at 20:42
160
    echo "US/Central - 10:26 PM (CST)" | sed -n "s/^.*-\s*\(\S*\).*$/\1/p"

-n      suppress printing
s       substitute
^.*     anything at the beginning
-       up until the dash
\s*     any space characters (any whitespace character)
\(      start capture group
\S*     any non-space characters
\)      end capture group
.*$     anything at the end
\1      substitute 1st capture group for everything on line
p       print it
jgshawkey
  • 2,034
  • 1
  • 9
  • 8
  • 27
    I feel like this made me an instant sed master. One good option I can tweak is better than nine I don't understand. – Noumenon Jan 11 '17 at 22:04
  • 2
    Thanks for the detailed explanation, helps to avoid future "how do I regexp XXXX" posts. – studgeek Feb 08 '17 at 20:23
  • 5
    Could you explain why you first suppress printing with `-n` then request printing again with `/p`? Wouldn't it be the same to omit the `-n` flag and omit the `/p` directive? Thanks. – Victor Zamanian Aug 02 '17 at 08:18
  • 4
    @VictorZamanian from [here](http://www.grymoire.com/Unix/Sed.html#uh-9): "By default, sed prints every line. If it makes a substitution, the new text is printed instead of the old one. If you use an optional argument to sed, "sed -n," it will not, by default, print any new lines. ... When the "-n" option is used, the "p" flag will cause the modified line to be printed." – tdashroy Nov 12 '19 at 20:21
  • @VictorZamanian here it doesn't make a difference here because there's only one line of input. But when trying to extract a substring from a multi-line input or file, this common technique makes sure that only the substring is printed and not every other line that `sed` had to search through. – Levi Uzodike Mar 26 '20 at 16:45
  • 2
    It outputs an empty line on macOS both in bash and zsh – Finesse May 19 '20 at 04:46
  • 1
    A rewarding experience to find a gold nugget after digging through a pile of rubbish. Thank you. – Industrial Control Freak Nov 19 '20 at 21:42
  • It says right in the question, "Anybody knows of a way of doing this only with bash - without using sed, awk, etc?" – miken32 Jul 19 '21 at 19:22
38

Quick 'n dirty, regex-free, low-robustness chop-chop technique

string="US/Central - 10:26 PM (CST)"
etime="${string% [AP]M*}"
etime="${etime#* - }"
doubleDown
  • 8,048
  • 1
  • 32
  • 48
  • 8
    That is so disgustingly dirty that I'm ashamed I didn't think of it myself. +1 `| read zone dash time apm zone` works too – Orwellophile Apr 30 '15 at 01:05
  • Very clean, and avoids calls to external programs. – Victor Zamanian Aug 02 '17 at 08:20
  • 25
    Hi, this would be 10x more useful if it included a reference to further documentation or some names around the technique so that people could go off and research more. For the interested, this is bash string manipulation, and you can find more details here: https://www.tldp.org/LDP/abs/html/string-manipulation.html – Pedro Mata-Mouros Oct 15 '18 at 15:38
6

If your string is

foo="US/Central - 10:26 PM (CST)"

then

echo "${foo}" | cut -d ' ' -f3

will do the job.

LarsTech
  • 80,625
  • 14
  • 153
  • 225
LeChatDeNansen
  • 133
  • 1
  • 5
  • 2
    or `cut -c14-18` of course only as long as the character position isn't changing. which shouldn't happen if the Timezone is fixed. – Markus Jan 21 '20 at 12:58
  • doesn't answer the question which specifically asks for a regex based solution – Aurovrata Aug 04 '22 at 08:18
  • 1
    @Aurovrata : Yes, you're right. So I would suggest : tim=$(print -- "${foo}" | grep -Eo "[[:digit:]]+:[[:digit:]]+") ; # assuming every record has the same format ... but this is not BASH but ksh – LeChatDeNansen Aug 12 '22 at 17:34
0

No need to open a pipe and spawn sed or awk to extract the 10:26 (time) part. Bash can easily handle this.

input="US/Central - 10:26 PM (CST)"
[[ $input =~ ([0-9]+:[0-9]+) ]]
echo ${BASH_REMATCH[1]}

Outputs:

10:26

If you're using zsh, it's the same, except the match result will be in $match[1] instead of $BASH_REMATCH[1]

In 2023, I don't think the extra pipe to grep, sed, awk or perl are relevant, especially when the question is:

Anybody knows of a way of doing this only with bash - without using sed, awk, etc?

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
erwin
  • 442
  • 6
  • 13
-1

foo="US/Central - 10:26 PM (CST)"

echo ${foo} | date +%H:%M

Jimbro
  • 1
  • 2
    Hello Jimbro, welcome to StackOverflow! Unfortunately this is not the solution to the problem. Note, that OP wants to extract the date from the string and your solution returns the current date. – Mikołaj Głodziak Jul 19 '21 at 21:53