3

I have a variable x which contains a string like "2025-12-13-04-32 Hello StackOverflow programmers".

How can I split it in several variables like this:

d=2015
m=12
dy=13 
h=04
mi=32 
t="Hello StackOverflow programmers"
0

4 Answers 4

10

A single read command will do:

input='2025-12-13-04-32 Hello stackoverflow programmers'

IFS='- ' read -r d m dy h mi t <<<"$input"

Note: <<< is a so-called here-string, which allows providing a regular string [variable] via stdin, related to the multi-line here-doc (something starting with, e.g., <<EOF). Here it is the shorter and more efficient alternative to echo "$input" | IFS='- ' read ...

  • IFS='- ' causes read to split the input line into tokens by either a space or a -.
  • Even though that would normally also break the string 'Hello stackoverflow programmers' into 3 tokens, it doesn't here, because read assigns the remainder of the line to the last variable specified, in case there aren't enough variables to match the resulting tokens; thus, variable $t receives 'Hello stackoverflow programmers', as desired.

To print the results, use the following:

Note that ${!name} is an instance of variable indirection - accessing a variable through another variable that contains its name.

names=( d m dy h mi t )
for name in "${names[@]}"; do
  printf '%s=%s\n' "$name" "${!name}"
done

This yields:

d=2025
m=12
dy=13
h=04
mi=32
t=Hello stackoverflow programmers

A note on the choice of approach:

  • read is great for field-based parsing based on a set of literal separator characters (a caveat is that each instance of a non-whitespace separator char. counts).
    • With read -ra, you can even read all tokens into an array, without having to know the number of tokens in advance.
  • For more flexible parsing (based on a known number of tokens), consider use of =~ with regular expressions, as in Jahid's answer.
Sign up to request clarification or add additional context in comments.

3 Comments

Okey it was a problem with my script it works fine. Thanks.
Here-string is not quite an equivalent of a pipe echo blah | stuff but rather an equivalent of a here-doc. Apart from that minor remark, your answer is definitely the best in OP's case.
@gniourf_gniourf. Thanks; I've added a reference to here-docs and changed 'equivalent' to 'alternative'.
5

Using Bash regex:

#!/bin/bash
s='2025-12-13-04-32 Hello stackoverflow programmers'
pat="([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)[[:space:]]*(.*)"
[[ $s =~ $pat ]]
echo "${BASH_REMATCH[1]}"
echo "${BASH_REMATCH[2]}"
echo "${BASH_REMATCH[3]}"
echo "${BASH_REMATCH[4]}"
echo "${BASH_REMATCH[5]}"
echo "${BASH_REMATCH[6]}"

Output:

2025
12
13
04
32
Hello stackoverflow programmers

3 Comments

For me, it doesn't show "Hello stackoverflow programmers".
Okey it was a problem with my script it works fine. Thanks. I did for x in $(ls $1)
++ for potentially more flexible parsing via =~; You could additionally create (and print) the individual variables as follows: names=(d m dy h mi t); i=0; for (( i = 0; i < "${#names[@]}"; i++ )); do declare "${names[i]}=${BASH_REMATCH[i+1]}"; printf '%s=%s\n' "${names[i]}" "${!names[i]}"; done; @Craxxurz: It's better not to use for to parse command output
1

To automatically assign whitespace separated values to variable names, you could do this:

x="2025-12-13-04-32 Hello stackoverflow programmers"
read -r d m dy h mi <<< "$x"

But since the beginning of the string contains dash signs (-) you first have to replace them with whitespaces:

x="2025-12-13-04-32 Hello stackoverflow programmers"
x="$(echo "$x" | sed 's/-/ /g')"
read -r d m dy h mi <<< "$x"

Still you have to adjust the number of variable names ;-)

I would say this is what you want:

x="2025-12-13-04-32 Hello stackoverflow programmers"                                                                                        
x="$(echo "$x" | tr '-' ' ')"                                                                                                               
read -r d m dy h mi t <<< "$x"                                                                                                          
echo $d
2025
echo $m
12
echo $dy
13
echo $h 
04
echo $mi
32
echo $t
Hello stackoverflow programmers

1 Comment

Instead of an external sed call, you could do the replacement with a parameter expansion: x="${x//-/ }".
0

Using parameter substitution and declare:

#!/bin/bash
x="2025-12-13-04-32 Hello stackoverflow programmers"

Shift () {
    declare -g "$1=${copy%%-*}"
    copy=${copy#*-}
}

copy=$x

for var in d m dy h mi ; do
    Shift $var
done

mi=${mi%% *} # Remove the message
t=${copy#* }

for var in d m dy h mi t ; do
    echo $var ${!var}
done

1 Comment

Just a heads-up for people stuck with Bash 4.1 or lower: declare -g is not supported; without -g, declare creates a local variable inside a function. Generally, simply omitting declare also creates a global variable, but since a parameter value is used as the variable name here, declare is required. An alternative that works down to (at least) Bash 3.x is to use printf -v to create the global variable: printf -v "$1" %s "${copy%%-*}".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.