У меня есть строка в Bash shell script, которую я хочу разбить на массив символов, а не на разделитель, а всего на один символ на индекс массива. Как я могу это сделать? В идеале он не будет использовать какие-либо внешние программы. Позвольте мне перефразировать это. Моя цель - переносимость, поэтому такие вещи, как sed
, которые, вероятно, будут находиться в любой совместимой с POSIX системе, являются прекрасными.
Bash: Разделить строку на массив символов
Ответ 1
Try
echo "abcdefg" | fold -w1
Изменить: добавлено более элегантное решение, предлагаемое в комментариях.
echo "abcdefg" | grep -o .
Ответ 2
Вы можете получить доступ к каждой букве отдельно уже без преобразования массива:
$ foo="bar"
$ echo ${foo:0:1}
b
$ echo ${foo:1:1}
a
$ echo ${foo:2:1}
r
Если этого недостаточно, вы можете использовать что-то вроде этого:
$ bar=($(echo $foo|sed 's/\(.\)/\1 /g'))
$ echo ${bar[1]}
a
Если вы даже не можете использовать sed
или что-то в этом роде, вы можете использовать первый метод выше в сочетании с циклом while, используя исходную длину строки (${#foo}
), чтобы построить массив.
Предупреждение: приведенный ниже код не работает, если строка содержит пробелы. Я думаю, ответ Вана Катона имеет больше шансов выжить со специальными символами.
thing=($(i=0; while [ $i -lt ${#foo} ] ; do echo ${foo:$i:1} ; i=$((i+1)) ; done))
Ответ 3
Если ваша строка хранится в переменной x, это создает массив y с отдельными символами:
i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1}; i=$((i+1));done
Ответ 4
В качестве альтернативы итерации над 0.. ${#string}-1
с циклом for/while есть два других способа, которые я могу сделать, используя только bash: using =~
и using printf
. (Существует третья возможность использования выражения eval
и {..}
, но это не имеет ясности.)
С правильной средой и поддержкой NLS в bash они будут работать с не-ASCII, как можно надеяться, удаляя потенциальные источники сбоев старыми системными инструментами, такими как sed
, если это вызывает беспокойство. Они будут работать от bash-3.0 (выпущено в 2005 году).
Использование =~
и регулярных выражений, преобразование строки в массив в одном выражении:
string="wonkabars"
[[ "$string" =~ ${string//?/(.)} ]] # splits into array
printf "%s\n" "${BASH_REMATCH[@]:1}" # loop free: reuse fmtstr
declare -a arr=( "${BASH_REMATCH[@]:1}" ) # copy array for later
То, как это работает, - это выполнить расширение string
которая заменяет каждый отдельный символ для (.)
, А затем сопоставляет это сгенерированное регулярное выражение с группировкой для захвата каждого отдельного символа в BASH_REMATCH[]
. Индекс 0 установлен на всю строку, так как этот специальный массив доступен только для чтения, вы не можете его удалить, обратите внимание :1
когда массив расширен, чтобы пропустить индекс 0, если это необходимо. Некоторые быстрые тесты для нетривиальных строк (> 64 символа) показывают, что этот метод существенно быстрее, чем один, используя операции bash и операции с массивом.
Вышеупомянутое будет работать со строками, содержащими новые строки, =~
поддерживает POSIX ERE, где .
соответствует по умолчанию, за исключением NUL, т.е. регулярное выражение компилируется без REG_NEWLINE
. (Поведение утилит обработки текста POSIX по умолчанию по-разному, и обычно это.)
Второй вариант, используя printf
:
string="wonkabars"
ii=0
while printf "%s%n" "${string:ii++:1}" xx; do
((xx)) && printf "\n" || break
done
Этот цикл увеличивает индекс ii
для печати одного символа за раз и разрывается, когда символов нет. Это было бы еще проще, если бы bash printf
вернул количество символов, напечатанных (как в C), а не статус ошибки, вместо этого количество напечатанных символов фиксируется в xx
с помощью %n
. (Это работает как минимум до bash-2.05b.)
С bash-3.1 и printf -v var
вас есть немного больше гибкости и вы можете не упасть с конца строки, если вы делаете что-то другое, кроме печати символов, например, для создания массива:
declare -a arr
ii=0
while printf -v cc "%s%n" "${string:(ii++):1}" xx; do
((xx)) && arr+=("$cc") || break
done
Ответ 5
string=hello123
for i in $(seq 0 ${#string})
do array[$i]=${string:$i:1}
done
echo "zero element of array is [${array[0]}]"
echo "entire array is [${array[@]}]"
Нулевой элемент массива [h]
. Весь массив [h e l l o 1 2 3 ]
.
Ответ 6
Самое простое, полное и элегантное решение:
$ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')
и тест
$ echo ${ARRAY[0]}
a
$ echo ${ARRAY[1]}
b
Объяснение: read -a
читает stdin как массив и назначает его переменной ARRAY, обрабатывая пробелы как разделитель для каждого элемента массива.
Оценка повторения строки для sed просто добавляет необходимые пробелы между каждым символом.
Мы используем Здесь String (< < < <) для подачи stdin прочитанного команда.
Ответ 7
Если текст может содержать пробелы:
eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
Ответ 8
$ echo hello | awk NF=NF FS=
h e l l o
или
$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
Ответ 9
Если вы хотите сохранить это в массиве, вы можете сделать это:
string=foo
unset chars
declare -a chars
while read -N 1
do
chars[${#chars[@]}]="$REPLY"
done <<<"$string"x
unset chars[$((${#chars[@]} - 1))]
unset chars[$((${#chars[@]} - 1))]
echo "Array: ${chars[@]}"
Array: f o o
echo "Array length: ${#chars[@]}"
Array length: 3
Окончательный x
необходим для обработки факта добавления новой строки после $string
, если она не содержит ее.
Если вы хотите использовать символы, разделенные NUL, вы можете попробовать следующее:
echo -n "$string" | while read -N 1
do
printf %s "$REPLY"
printf '\0'
done
Ответ 10
AWK довольно удобен:
a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'
где FS
и OFS
- это разделитель для чтения и распечатки
Ответ 11
Для тех, кто приземлился здесь, ища, как это сделать в рыбе:
Мы можем использовать встроенную string
команду (начиная с V2.3.0) для работы со строками.
↪ string split '' abc
a
b
c
Вывод - это список, поэтому операции массива будут работать.
↪ for c in (string split '' abc)
echo char is $c
end
char is a
char is b
char is c
Здесь более сложный пример, повторяющий строку с индексом.
↪ set --local chars (string split '' abc)
for i in (seq (count $chars))
echo $i: $chars[$i]
end
1: a
2: b
3: c
Ответ 12
Если вам также нужна поддержка строк с символами новой строки, вы можете сделать следующее:
str2arr(){ local string="$1"; mapfile -d $'\0' Chars < <(for i in $(seq 0 $((${#string}-1))); do printf '%s\u0000' "${string:$i:1}"; done); printf '%s' "(${Chars[*]@Q})" ;}
string=$(printf '%b' "apa\nbepa")
declare -a MyString=$(str2arr "$string")
declare -p MyString
# prints declare -a MyString=([0]="a" [1]="p" [2]="a" [3]=$'\n' [4]="b" [5]="e" [6]="p" [7]="a")
В ответ на Александра де Оливейра я думаю, что следующее выглядит более элегантно или, по крайней мере, более интуитивно понятно:
while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
Ответ 13
zsh решение: поместить скалярную переменную string
в arr
, который будет массивом:
arr=(${(ps::)string})