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

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

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

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

11 ответов

Решение

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

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

This
is
a
sentence.

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

    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

Вероятно, самый простой и безопасный способ в 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") затем прочитайте экранирование. Это оставлено как упражнение для читателя.


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

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

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% всех потребностей.

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

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

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

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

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

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

Это печатает

[Это]
[является]
[А]
[тестовое задание]
$ 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
echo $WORDS | xargs -n1 echo

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

(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

$ echo foo bar baz | sed 's/ /\n/g'

      foo
bar
baz

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

[[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"

Для моего варианта использования лучшим вариантом был:

      grep -oP '\w+' file

По сути, это регулярное выражение, которое соответствует непрерывным непробельным символам. Это означает, что любой тип и любое количество пробелов не будут совпадать. Параметр -o выводит каждое совпадение слов в отдельной строке.

Другой подход к этому (с использованием Perl):

      $ echo foo bar baz | perl -nE 'say for split /\s/'
foo
bar
baz
Другие вопросы по тегам