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"
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 -.'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).
read -ra, you can even read all tokens into an array, without having to know the number of tokens in advance.=~ with regular expressions, as in Jahid's answer.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.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
=~; 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 outputTo 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
x="${x//-/ }".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
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%%-*}".