Как разбить одну строку на несколько строк, разделенных хотя бы одним пробелом в оболочке bash?

224

У меня есть строка, содержащая много слов, по крайней мере, один пробел между каждыми двумя. Как я могу разбить строку на отдельные слова, чтобы я мог проходить через них?

Строка передается в качестве аргумента. Например ${2} == "cat cat file". Как я могу пройти через это?

Кроме того, как я могу проверить, содержит ли строка пробелы?

derrdji
источник
1
Что за оболочка? Bash, cmd.exe, powershell ...?
Алексей Свиридов
Вам просто нужно зациклить (например, выполнить команду для каждого из слов)? Или вам нужно сохранить список слов для последующего использования?
ДВК,

Ответы:

281

Вы пытались просто передать строковую переменную в forцикл? Bash, например, будет автоматически разделяться на пустые места.

sentence="This is   a sentence."
for word in $sentence
do
    echo $word
done

 

This
is
a
sentence.
чернь
источник
1
@MobRule - единственный недостаток этого заключается в том, что вы не можете легко захватить (по крайней мере, я не помню способ) вывод для дальнейшей обработки. См. Мое решение "tr" ниже для чего-то, что отправляет материал в STDOUT
DVK
4
Вы можете просто добавить его в переменной: A=${A}${word}).
Лукас Джонс
1
установить $ text [это поместит слова в $ 1, $ 2, $ 3 ... и т. д.]
Раджеш
32
На самом деле этот трюк является не только неправильным решением, но и чрезвычайно опасным из-за разболтанности оболочки. touch NOPE; var='* a *'; for a in $var; do echo "[$a]"; doneвыходные данные [NOPE] [a] [NOPE]вместо ожидаемых [*] [a] [*](LFs заменены на SPC для удобства чтения).
Тино
@mob Что мне делать, если я хочу разбить строку на основе какой-то конкретной строки? пример разделителя ".xlsx" .
296

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

sentence="this is a story"
stringarray=($sentence)

теперь вы можете получить доступ к отдельным элементам напрямую (начинается с 0):

echo ${stringarray[0]}

или преобразовать обратно в строку для цикла:

for i in "${stringarray[@]}"
do
  :
  # do whatever on $i
done

Конечно, раньше было получено ответное сообщение о том, что цикл проходил напрямую, но недостатком этого ответа было то, что он не отслеживал отдельные элементы для последующего использования:

for i in $sentence
do
  :
  # do whatever on $i
done

Смотрите также Bash Array Reference .

Highwind
источник
26
К сожалению, не совсем идеально, из-за срыва оболочки: touch NOPE; var='* a *'; arr=($var); set | grep ^arr=результаты arr=([0]="NOPE" [1]="a" [2]="NOPE")вместо ожидаемыхarr=([0]="*" [1]="a" [2]="*")
Тино
@Tino: если вы не хотите, чтобы шатание мешало, просто выключите его. Тогда решение будет отлично работать и с подстановочными знаками. Это лучший подход на мой взгляд.
Александрос
3
@Alexandros Мой подход состоит в том, чтобы использовать только шаблоны, которые по умолчанию безопасны и прекрасно работают в любом контексте. Требование изменить глобализацию оболочки для получения безопасного решения - это не просто очень опасный путь, это уже темная сторона. Поэтому я советую никогда не привыкать использовать такой шаблон здесь, потому что рано или поздно вы забудете о некоторых деталях, а затем кто-нибудь воспользуется вашей ошибкой. Вы можете найти доказательства для таких подвигов в прессе. Каждый. Не замужем. День.
Тино
86

Просто используйте встроенные оболочки "set". Например,

установить $ текст

После этого отдельные слова в $ text будут в $ 1, $ 2, $ 3 и т. Д. Для устойчивости обычно делают

set - мусорный текст
сдвиг

обработать случай, когда $ text пуст или начать с тире. Например:

text = "Это тест"
set - мусорный текст
сдвиг
для слова; делать
  echo "[$ word]"
сделано

Это печатает

[Это]
[является]
[А]
[тест]
Idelic
источник
5
Это отличный способ разделить переменную так, чтобы к отдельным частям можно было обращаться напрямую. +1; решил мою проблему
Cheekysoft
Я собирался предложить использовать, awkно setгораздо проще. Теперь я setфанат. Спасибо @ Идеально!
Измир Рамирес
22
Пожалуйста, будьте внимательны, если вы делаете такие вещи: touch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; doneвывод [NOPE] [a] [NOPE]вместо ожидаемого [*] [a] [*]. Используйте его, только если вы на 101% уверены, что в разделенной строке метасимволы SHELL отсутствуют!
Тино
4
@Tino: Эта проблема применяется везде, не только здесь, но в этом случае вы могли бы просто set -fдо set -- $varи set +fпосле отключить сглаживание.
Идеально
3
@ Идеально: Хороший улов. С set -fтвоим решением тоже безопасно. Но set +fэто значение по умолчанию для каждой оболочки, поэтому это важная деталь, на которую следует обратить внимание, потому что другие, вероятно, не знают об этом (как я тоже).
Тино
81

Вероятно, наиболее простой и безопасный способ в BASH 3 и выше:

var="string    to  split"
read -ra arr <<<"$var"

(где arrмассив, который принимает разделенные части строки) или, если во вводе могут быть символы новой строки, и вам нужно больше, чем просто первая строка:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(обратите внимание на пробел -d '', он не может быть пропущен), но это может дать вам неожиданный символ новой строки от <<<"$var"(поскольку это неявно добавляет LF в конце).

Пример:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

Выходит ожидаемый

[*]
[a]
[*]

так как это решение (в отличие от всех предыдущих решений здесь) не подвержено неожиданному и часто неконтролируемому выкалыванию оболочки.

Также это дает вам всю мощь IFS, как вы, вероятно, хотите:

Пример:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

Выводит что-то вроде:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

Как видите, пробелы можно сохранить и таким образом:

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

выходы

[ split  ]
[   this    ]

Обратите внимание, что обработка IFSв BASH сама по себе является предметом, поэтому проведите тесты, некоторые интересные темы на эту тему:

  • unset IFS: Игнорирует прогоны SPC, TAB, NL и начинается и заканчивается на линии
  • IFS='': Без разделения полей, просто все читает
  • IFS=' ': Запускает SPC (и только SPC)

Последний пример

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

выходы

1 [this is]
2 [a test]

пока

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

выходы

1 [this]
2 [is]
3 [a]
4 [test]

КСТАТИ:

  • Если вы не привыкли к $'ANSI-ESCAPED-STRING'этому, это экономит время.

  • Если вы не включаете -r(как в read -a arr <<<"$var"), тогда read выполняет обратную косую черту. Это оставлено как упражнение для читателя.


По второму вопросу:

Чтобы проверить что-то в строке, я обычно придерживаюсь case, так как это может проверять сразу несколько случаев (примечание: case выполняет только первое совпадение, если вам нужно упасть, используйте caseоператоры multiplce ), и это часто случается (pun предназначена):

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

Таким образом, вы можете установить возвращаемое значение для проверки SPC следующим образом:

case "$var" in (*' '*) true;; (*) false;; esac

Почему case? Поскольку он обычно более читабелен, чем последовательности регулярных выражений, и благодаря метасимволам Shell он хорошо обрабатывает 99% всех потребностей.

Tino
источник
2
Этот ответ заслуживает большего числа голосов, из-за выделенных проблем и его всесторонности
Брайан Агнью
@ Брайан Спасибо. Обратите внимание, что вы можете использовать set -fили set -o noglobдля переключения глобализации, чтобы метасимволы оболочки больше не наносили вреда в этом контексте. Но я на самом деле не дружу с этим, так как это оставляет много возможностей оболочки / очень подвержено ошибкам при переключении назад и вперед в этом параметре.
тино
2
Замечательный ответ, действительно заслуживает большего количества голосов. Дополнительное замечание о падении дела - вы можете ;&добиться этого. Не совсем уверен, в какой версии bash это появилось. Я пользователь 4.3
Сергей Колодяжный
2
@Serg спасибо за замечание, так как я этого еще не знал! Так что я посмотрел, он появился в Bash4 . ;&это принудительное падение без проверки шаблона, как в C. И есть также то, ;;&что просто продолжает делать дальнейшие проверки шаблона. Так ;;это как if ..; then ..; else if ..и ;;&как if ..; then ..; fi; if .., где ;&это похоже m=false; if ..; then ..; m=:; fi; if $m || ..; then ..- никто не перестает учиться (у других);)
Тино
@Tino Это абсолютно верно - обучение - это непрерывный процесс. На самом деле, я не знал, ;;&прежде чем вы прокомментировали: D Спасибо, и пусть снаряд будет с вами;)
Сергей Колодяжный
43
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

Для проверки пробелов используйте grep:

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1
DVK
источник
1
В BASH echo "X" |обычно может быть заменен <<<"X", например: grep -s " " <<<"This contains SPC". Вы можете заметить разницу, если вы делаете что-то вроде echo X | read varв отличие от read var <<< X. Только последняя импортирует переменную varв текущую оболочку, а для доступа к ней в первом варианте необходимо сгруппировать ее так:echo X | { read var; handle "$var"; }
Tino
17

(A) Чтобы разделить предложение на слова (разделенные пробелами), вы можете просто использовать IFS по умолчанию, используя

array=( $string )


Пример выполнения следующего фрагмента

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

будет выводить

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

Как вы можете видеть, вы можете использовать одинарные или двойные кавычки тоже без проблем.

Примечания:
- это в основном то же самое, что и ответ моба , но таким образом вы сохраняете массив для любых дальнейших нужд. Если вам нужен только один цикл, вы можете использовать его ответ, который на одну строку короче :)
- пожалуйста, обратитесь к этому вопросу для поиска альтернативных методов разделения строки на основе разделителя.


(B) Чтобы проверить наличие символа в строке, вы также можете использовать совпадение регулярного выражения.
Пример для проверки наличия пробела вы можете использовать:

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi
Люка Боррионе
источник
Для подсказки регулярного выражения (B) +1, но -1 для неправильного решения (A), так как это подвержено ошибкам, вызывающим сбои оболочки. ;)
Тино
6

Для проверки пробелов только с помощью bash:

[[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"
Гленн Джекман
источник
1
echo $WORDS | xargs -n1 echo

Это выводит каждое слово, вы можете обработать этот список так, как считаете нужным.

Álex
источник