8

I want to split 'hello' into h e l l o in an array using only bash, I could do it in sed with sed 's/./& /g' but I want to know how to split a string into an array in Bash when I do not know what the delimiter would be, or the delimiter is any single character. I don't think I can use ${i// /} without some creativity because the delimiter is an unknown, and I don't think that expression accepts regex. I tried using BASH_REMATCH with [[ string =~ ([a-z].).* ]] but it doesn't work as I expected. What is the proper way to use only bash to accomplish a string.split() type of behavior? The reason is that I am trying to write the rev utility in all bash:

  while read data; do
  word=($(echo $data|tr ' ' '_'|sed 's/./& /g'))
  new=()
  i=$((${#word[@]} - 1))
  while [[ $i -ge 0 ]]; do
    new+=(${word[$i]})
    (( i-- ))
  done
  echo ${new[@]}|tr -d ' '|tr '_' ' '
  done

But I used tr and sed, I want to know how to do the split properly and then I will fix it to be all bash. Just for fun.

  • There must be cross-site duplicates on Stack Overflow, given the size of it. One candidate is Bash: Split string into character array. – Peter Mortensen Apr 19 '15 at 22:34
  • [[ $string =~ ${string//?/(.)} ]] will set BASH_REMATCH[] as required, see my answer to the question Peter Mortensen links to for an explanation. – mr.spuratic Feb 5 '16 at 17:53
11
s="hello"
declare -a a   # define array a
for ((i=0; i<${#s}; i++)); do a[$i]="${s:$i:1}"; done
declare -p a   # print array a in a reusable form

Output:

declare -a a='([0]="h" [1]="e" [2]="l" [3]="l" [4]="o")'

or

s="hello"
while read -n 1 c; do a+=($c); done  <<< "$s"
declare -p a

Output:

declare -a a='([0]="h" [1]="e" [2]="l" [3]="l" [4]="o")'
  • 1
    The second one is nice but it only works (in this case) because you don't quote "$c", so it will drop whitespace and expand stars in $s. – rici Apr 20 '15 at 3:14
  • 2
    The second option should be: while read -N 1 c; do a+=("$c"); done <<< "$s". The -N allows to read even newlines, and the quotes ("$c") avoid pathname expansion (and some other issues). – user79743 Aug 16 '15 at 23:05
  • 1
    Of course, if the last newline must be corrected, change the line given to: while read -N 1 c; do a+=("$c"); done <<< "$s"x and then erase the last newline and the x added: unset a[${#a[@]}-1]; unset a[${#a[@]}-1]. – user79743 Aug 16 '15 at 23:20
4

To split string into array of characters, with null delimiter, you can:

str='hello'
arr=()
i=0
while [ "$i" -lt "${#str}" ]; do
  arr+=("${str:$i:1}")
  i=$((i+1))
done

printf '%s\n' "${arr[@]}"

With delimiter other than null, you can:

set -f
str='1,2,3,4,5'
IFS=',' arr=($str)
printf '%s\n' "${arr[@]}"
  • If IFS can be set, then you can just do: IFS=',' arr=($str). – muru Apr 19 '15 at 16:46
  • 2
    @muru: You will be stucked with glob in that case. Updated with disabling glob. – cuonglm Apr 19 '15 at 16:50
  • @BinaryZebra: Ah, yes, my bad, missing quote. Fixed it, thanks. – cuonglm Aug 17 '15 at 1:35
2

Just for fun (and other shells) other variant:

word=hello
unset letter
while [ ${#word} -gt 0 ]
do
    rest=${word#?}
    letter[${#letter[*]}]=${word%$rest}
    word=$rest
done

And check

for l in "${!letter[@]}"
do
    echo "letter [$l] = ${letter[l]}"
done

will print

letter [0] = h
letter [1] = e
letter [2] = l
letter [3] = l
letter [4] = o
0

Method 1:

Oneliner:

s="hello"
for ((i=0;i<${#s};i++)); do result[$i]="${s:i:1}"; done
echo ${result[@]}

Expanded code:

s="hello"                   # Original string.

for ((i=0;i<${#s};i++)); do # For i=0; i<(length of s); i++
    result[$i]="${s:i:1}"       # result[i] is equal to the i th character of $s
done                        # End of the loop

echo ${result[@]} # Print all elements of $result.

Method 2:

Oneliner:

s="hello"
var=($(while read -n 1; do printf "$REPLY "; done <<< "$s"))
echo "${var[@]}"

Expanded code:

s="hello" # Original string.

while read -n 1; do # Read chraracter by character the string.
    space_separated=$(printf "$space_separated $REPLY") # $space_separated is equal to it plus the current character.
done <<< "$s" # Pass the string to the loop

result=($space_separated) # Split $space_separated into an array.

echo "${result[@]}" # Print all elements of $result.

Thanks to @cuonglm by it's suggestion.
Effectively, you can use $REPLY that is the default varible where read reads the input.

  • 1
    You can use $REPLY instead of char variable. – cuonglm Apr 19 '15 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.