Split string by delimiter and get N-th element

Use cut with _ as the field delimiter and get desired fields:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

You can also use echo and pipe instead of Here string:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Example:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

Beware that if $s contains newline characters, that will return a multiline string that contains the 2nd/4th field in each line of $s, not the 2nd/4th field in $s.


Wanted to see an awk answer, so here's one:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

Using only POSIX sh constructs, you can use parameter substitution constructs to parse one delimiter at a time. Note that this code assumes that there is the requisite number of fields, otherwise the last field is repeated.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Alternatively, you can use an unquoted parameter substitution with wildcard expansion disabled and IFS set to the delimiter character (this only works if the delimiter is a single non-whitespace character or if any whitespace sequence is a delimiter).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

This clobbers the positional parameters. If you do this in a function, only the function's positional parameters are affected.

Yet another approach for strings that don't contain newline characters is to use the read builtin.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF