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

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

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

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

Ответ 1

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

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

 

This
is
a
sentence.

Ответ 2

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

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.

Ответ 3

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

set $text

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

set -- junk $text
shift

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

text="This is          a              test"
set -- junk $text
shift
for word; do
  echo "[$word]"
done

Отпечатает

[This]
[is]
[a]
[test]

Ответ 4

Вероятно, самый простой и безопасный способ в 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]

BTW:

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

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


Во второй вопрос:

Чтобы проверить что-то в строке, я обычно придерживаюсь case, так как это может проверять сразу несколько случаев (примечание: case только выполняет первое совпадение, если вам нужно использовать инструкции multipull 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% всех потребностей.

Ответ 5

$ 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

Ответ 6

(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

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

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


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

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi

Ответ 7

Для проверки пробелов просто с bash:

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