10

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.

2
  • There must be cross-site duplicates on Stack Overflow, given the size of it. One candidate is Bash: Split string into character array. Apr 19, 2015 at 22:34
  • [[ $string =~ ${string//?/(.)} ]] will set BASH_REMATCH[] as required, see my answer to the question Peter Mortensen links to for an explanation. Feb 5, 2016 at 17:53

5 Answers 5

13
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 (please note the comments)

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")'
3
  • 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, 2015 at 3:14
  • 3
    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, 2015 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, 2015 at 23:20
5

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[@]}"
3
  • If IFS can be set, then you can just do: IFS=',' arr=($str).
    – muru
    Apr 19, 2015 at 16:46
  • 2
    @muru: You will be stucked with glob in that case. Updated with disabling glob.
    – cuonglm
    Apr 19, 2015 at 16:50
  • @BinaryZebra: Ah, yes, my bad, missing quote. Fixed it, thanks.
    – cuonglm
    Aug 17, 2015 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
  • 1
    You can use $REPLY instead of char variable.
    – cuonglm
    Apr 19, 2015 at 17:01
0

This is the answer: https://stackoverflow.com/a/34634535/2332068

  [[ "${text}" =~ ${text//?/(.)} ]] && array=("${BASH_REMATCH[@]:1}")

I used on bash 4.3 this replacement for ${data@Q}

shell-escape-to() {
  local REPLY="${*:2}"
  [[ "${*:2}" =~ ${REPLY//?/(.)} ]] && printf -v "$1" "\\%c" "${BASH_REMATCH[@]:1}"
}
...
shell-escape-to dir_ "$dir"
sg "$GROUP" "exec mkdir -m 775 -p ${dir_}"

bash 4.4 and above would be: sg "$GROUP" "exec mkdir -m 775 -p ${dir@Q}"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .