У меня есть такая переменная:
words="这是一条狗。"
Я хочу , чтобы сделать цикл по каждому из персонажей, один в то время, например , первый character="这"
, а затем character="是"
, character="一"
и т.д.
Единственный известный мне способ - вывести каждый символ в отдельную строку в файле, а затем использовать while read line
, но это кажется очень неэффективным.
- Как я могу обработать каждый символ в строке с помощью цикла for?
Ответы:
С
sed
наdash
оболочкеLANG=en_US.UTF-8
, я получил в следующем режиме работает правильно:$ echo "你好嗎 新年好。全型句號" | sed -e 's/\(.\)/\1\n/g' 你 好 嗎 新 年 好 。 全 型 句 號
и
$ echo "Hello world" | sed -e 's/\(.\)/\1\n/g' H e l l o w o r l d
Таким образом, вывод можно зациклить с помощью
while read ... ; do ... ; done
отредактированный для образца текста перевод на английский язык:
"你好嗎 新年好。全型句號" is zh_TW.UTF-8 encoding for: "你好嗎" = How are you[ doing] " " = a normal space character "新年好" = Happy new year "。全型空格" = a double-byte-sized full-stop followed by text description
источник
Вы можете использовать
for
цикл в стиле C :foo=string for (( i=0; i<${#foo}; i++ )); do echo "${foo:$i:1}" done
${#foo}
расширяется до длиныfoo
.${foo:$i:1}
расширяется до подстроки, начиная с позиции$i
длины 1.источник
bash
требует синтаксиса .for (( _expr_ ; _expr_ ; _expr_ )) ; do _command_ ; done
а не то же самое, что $ (( expr )) или (( expr )). Во всех трех конструкциях bash expr обрабатывается одинаково, а $ (( expr )) также является POSIX.bash
которые вычисляются в арифметическом контексте.${#var}
возвращает длинуvar
${var:pos:N}
возвращает N символовpos
начиная сПримеры:
$ words="abc" $ echo ${words:0:1} a $ echo ${words:1:1} b $ echo ${words:2:1} c
так что легко повторить.
по-другому:
$ grep -o . <<< "abc" a b c
или же
$ grep -o . <<< "abc" | while read letter; do echo "my letter is $letter" ; done my letter is a my letter is b my letter is c
источник
Я удивлен, что никто не упомянул очевидное
bash
решение, использующее толькоwhile
иread
.while read -n1 character; do echo "$character" done < <(echo -n "$words")
Обратите внимание на использование,
echo -n
чтобы избежать лишней новой строки в конце.printf
- еще один хороший вариант, который может больше подойти для ваших нужд. Если вы хотите игнорировать пробелы, замените их"$words"
на"${words// /}"
.Другой вариант есть
fold
. Обратите внимание, однако, что он никогда не должен попадать в цикл for. Вместо этого используйте цикл while следующим образом:while read char; do echo "$char" done < <(fold -w1 <<<"$words")
Основным преимуществом использования внешней
fold
команды ( пакета coreutils ) будет краткость. Вы можете передать его вывод другой команде, такой какxargs
(часть пакета findutils ), следующим образом:fold -w1 <<<"$words" | xargs -I% -- echo %
Вы захотите заменить
echo
команду, используемую в приведенном выше примере, на команду, которую вы хотите запускать для каждого персонажа. Обратите внимание, чтоxargs
по умолчанию отбрасываются пробелы. Вы можете использовать,-d '\n'
чтобы отключить это поведение.Интернационализация
Я только что протестировал
fold
некоторые азиатские символы и понял, что у них нет поддержки Unicode. Так что, хотя он подходит для нужд ASCII, он не сработает для всех. В этом случае есть несколько альтернатив.Я бы, наверное, заменил
fold -w1
массивом awk:awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'
Или
grep
команда, упомянутая в другом ответе:Спектакль
К вашему сведению, я проверил 3 вышеупомянутых варианта. Первые два были быстрыми, почти завязывающими, а петля сгиба была немного быстрее, чем петля while. Неудивительно, что он
xargs
был самым медленным ... в 75 раз медленнее.Вот (сокращенный) тестовый код:
words=$(python -c 'from string import ascii_letters as l; print(l * 100)') testrunner(){ for test in test_while_loop test_fold_loop test_fold_xargs test_awk_loop test_grep_loop; do echo "$test" (time for (( i=1; i<$((${1:-100} + 1)); i++ )); do "$test"; done >/dev/null) 2>&1 | sed '/^$/d' echo done } testrunner 100
Вот результаты:
источник
character
пуст для пробелов с помощью простогоwhile read
решения, что может быть проблематичным, если нужно различать разные типы пробелов.read -n1
наread -N1
необходимо для правильной обработки пробелов.Я считаю, что до сих пор нет идеального решения, которое бы правильно сохраняло все символы пробелов и было достаточно быстрым, поэтому я отправлю свой ответ. Использование
${foo:$i:1}
работает, но очень медленно, что особенно заметно на больших строках, как я покажу ниже.Моя идея - это расширение метода, предложенного Six , который включает в себя
read -n1
некоторые изменения для сохранения всех символов и правильной работы для любой строки:while IFS='' read -r -d '' -n 1 char; do # do something with $char done < <(printf %s "$string")
Как это устроено:
IFS=''
- Переопределение внутреннего разделителя полей на пустую строку предотвращает удаление пробелов и табуляции. Выполнение этого в той же строкеread
означает, что это не повлияет на другие команды оболочки.-r
- Означает «сырой», что предотвращаетread
обработку\
конца строки как специального символа конкатенации строк.-d ''
- Передача пустой строки в качестве разделителя предотвращаетread
удаление символов новой строки. Фактически означает, что в качестве разделителя используется нулевой байт.-d ''
равно-d $'\0'
.-n 1
- Означает, что будет читаться по одному символу.printf %s "$string"
- Использованиеprintf
вместоecho -n
безопаснее, потому чтоecho
относится-n
и-e
к опциям. Если вы передадите «-e» в виде строки,echo
ничего не напечатает.< <(...)
- Передача строки в цикл с помощью подстановки процесса. Если вы используете здесь-строки вместо (done <<< "$string"
), в конце добавляется дополнительный символ новой строки. Кроме того, передача строки через pipe (printf %s "$string" | while ...
) заставит цикл выполняться в подоболочке, что означает, что все операции с переменными являются локальными внутри цикла.Теперь давайте проверим производительность с огромной строкой. В качестве источника я использовал следующий файл:
https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
Следующий скрипт был вызван
time
командой:#!/bin/bash # Saving contents of the file into a variable named `string'. # This is for test purposes only. In real code, you should use # `done < "filename"' construct if you wish to read from a file. # Using `string="$(cat makefiles.txt)"' would strip trailing newlines. IFS='' read -r -d '' string < makefiles.txt while IFS='' read -r -d '' -n 1 char; do # remake the string by adding one character at a time new_string+="$char" done < <(printf %s "$string") # confirm that new string is identical to the original diff -u makefiles.txt <(printf %s "$new_string")
И вот результат:
Как видим, довольно быстро.
Затем я заменил цикл на цикл, в котором используется расширение параметров:
for (( i=0 ; i<${#string}; i++ )); do new_string+="${string:$i:1}" done
Выходные данные показывают, насколько велика потеря производительности:
Точные цифры могут сильно отличаться в разных системах, но общая картина должна быть похожей.
источник
Я тестировал это только со строками ascii, но вы могли бы сделать что-то вроде:
while test -n "$words"; do c=${words:0:1} # Get the first character echo character is "'$c'" words=${words:1} # trim the first character done
источник
Цикл стиля C в ответе @chepner находится в функции оболочки
update_terminal_cwd
, иgrep -o .
решение умное, но я был удивлен, не увидев решения, использующегоseq
. Вот мой:read word for i in $(seq 1 ${#word}); do echo "${word:i-1:1}" done
источник
Также можно разделить строку на массив символов, используя,
fold
а затем перебрать этот массив:for char in `echo "这是一条狗。" | fold -w1`; do echo $char done
источник
#!/bin/bash word=$(echo 'Your Message' |fold -w 1) for letter in ${word} ; do echo "${letter} is a letter"; done
Вот результат:
Y - это буква o - это буква u - это буква r - это буква M - это буква e - это буква s - это буква s - это буква a - это буква g - это буква е - это буква
источник
Чтобы перебирать символы ASCII в POSIX-совместимой оболочке, вы можете избежать внешних инструментов, используя расширение параметров:
#!/bin/sh str="Hello World!" while [ ${#str} -gt 0 ]; do next=${str#?} echo "${str%$next}" str=$next done
или же
str="Hello World!" while [ -n "$str" ]; do next=${str#?} echo "${str%$next}" str=$next done
источник
sed работает с юникодом
IFS=$'\n' for z in $(sed 's/./&\n/g' <(printf '你好嗎')); do echo hello: "$z" done
выходы
источник
Другой подход, если вас не волнует игнорирование пробелов:
for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do # Handle $char here done
источник
Другой способ:
Characters="TESTING" index=1 while [ $index -le ${#Characters} ] do echo ${Characters} | cut -c${index}-${index} index=$(expr $index + 1) done
источник
Делюсь своим решением:
read word for char in $(grep -o . <<<"$word") ; do echo $char done
источник
*
, вы получите файлы в текущем каталоге.TEXT="hello world" for i in {1..${#TEXT}}; do echo ${TEXT[i]} done
где
{1..N}
включающий диапазон${#TEXT}
это количество букв в строке${TEXT[i]}
- вы можете получить char из строки, как элемент из массиваисточник