Подтвердить что ты не робот

Bash: Разделить строку на массив символов

У меня есть строка в Bash shell script, которую я хочу разбить на массив символов, а не на разделитель, а всего на один символ на индекс массива. Как я могу это сделать? В идеале он не будет использовать какие-либо внешние программы. Позвольте мне перефразировать это. Моя цель - переносимость, поэтому такие вещи, как sed, которые, вероятно, будут находиться в любой совместимой с POSIX системе, являются прекрасными.

4b9b3361

Ответ 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})