Bash: разбить строку на массив символов - PullRequest
52 голосов
/ 28 сентября 2011

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

Ответы [ 13 ]

90 голосов
/ 28 сентября 2011

Попробуйте

echo "abcdefg" | fold -w1

Редактировать: Добавлено более элегантное решение, предложенное в комментариях.

echo "abcdefg" | grep -o .
28 голосов
/ 28 сентября 2011

Вы можете получить доступ к каждой букве индивидуально уже без преобразования массива:

$ 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))
9 голосов
/ 28 сентября 2011

Если ваша строка хранится в переменной x, это создает массив y с отдельными символами:

i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1};  i=$((i+1));done
7 голосов
/ 06 января 2016

В качестве альтернативы итерации по 0 .. ${#string}-1 с циклом for / while, есть два других способа сделать это с only bash : использовать =~ и использовать 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
2 голосов
/ 27 апреля 2017

Самое простое, полное и элегантное решение:

$ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')  

и тест

$ echo ${ARRAY[0]}
  a

$ echo ${ARRAY[1]}
  b

Объяснение : read -a считывает стандартный ввод в виде массива и присваивает его переменной ARRAY, рассматривая пробелы в качестве разделителя для каждого элемента массива.

Оценка вывода строки в sed просто добавляет необходимые пробелы между каждым символом.

Мы используем Здесь строка (<<<) для подачи в stdin команды чтения. </p>

2 голосов
/ 05 февраля 2017
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 ].

1 голос
/ 15 июня 2014
$ echo hello | awk NF=NF FS=
h e l l o

Или

$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
1 голос
/ 06 июня 2013

Если текст может содержать пробелы:

eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
0 голосов
/ 01 марта 2019

zsh решение: поместить скалярную переменную string в arr, которая будет массивом:

arr=(${(ps::)string})
0 голосов
/ 28 марта 2018

В ответ на Александра де Оливейра, я думаю, что следующее более элегантно или, по крайней мере, более интуитивно понятно:

while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...