Як розділити одну струну на кілька рядків, розділених принаймні одним пробілом в оболонці bash?


224

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

Рядок передається як аргумент. Напр ${2} == "cat cat file". Як я можу пройти цикл?

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


1
Яка шкаралупа? Bash, cmd.exe, powerhell ...?
Олексій Свиридов

Вам просто потрібно циклічно (наприклад, виконати команду для кожного зі слів)? Або вам потрібно зберігати список слів для подальшого використання?
DVK

Відповіді:


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] [*](КЧ замінені 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 .


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

Просто використовуйте вбудовані оболонки "набір". Наприклад,

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

Після цього окремі слова в $ тексті будуть становити $ 1, $ 2, $ 3 і т. Д. Для надійності зазвичай це

набір - мотлох $ текст
зрушення

обробляти випадок, коли $ текст порожній або починати з тире. Наприклад:

text = "Це тест"
набір - мотлох $ текст
зрушення
за слово; робити
  відлуння "[$ word]"
зроблено

Це відбитки

[Це]
[є]
[a]
[тест]

5
Це відмінний спосіб розділити вар, щоб отримати доступ до окремих частин безпосередньо. +1; вирішив мою проблему
Cheekysoft

Я збирався запропонувати використовувати, awkале setце набагато простіше. Зараз я setфанат. Дякую @Idelic!
Ізмір Рамірес

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
@Idelic: Хороший улов. З 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" (оскільки це неявно додає НЧ в кінці).

Приклад:

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    ]

Зауважте, що поводження з IFSBASH є предметом самостійно, тому зробіть свої тести, деякі цікаві теми з цього приводу:

  • 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 виконує лише першу відповідність, якщо вам потрібно пропустити використання caseзаяв multiplce ), і ця потреба досить часто буває (каламбур) призначений):

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% усіх потреб.


2
Ця відповідь заслуговує на більшу кількість результатів, завдяки висвітленим проблемам глобалізації та її всебічності
Брайан Агнеу

@brian Дякую Зверніть увагу, що ви можете використовувати set -fабо set -o noglobперемикати глобулінг, так що метахарактеристики оболонки більше не завдають шкоди в цьому контексті. Але я насправді не є цим другом, оскільки це залишає після себе велику потужність оболонки / дуже схильна до перемикання цієї настройки назад і назад.
Тіно

2
Чудова відповідь, справді заслуговує на більшу кількість нагород. Бічна примітка про провал справи - ви можете скористатися ;&цим. Не зовсім впевнений, у якій версії баш з'явився. Я 4,3 користувача
Сергій Колодяжний

2
@ Серг спасибі за те, що зауважив, оскільки я цього ще не знав! Тому я подивився, він з'явився в 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

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

Це виводить кожне слово, ви можете обробити цей список так, як потім вважаєте за потрібне.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.