В скрипте Bash я хотел бы разбить строку на части и сохранить их в массиве.
Линия:
Paris, France, Europe
Я хотел бы иметь их в массиве, как это:
array[0] = Paris
array[1] = France
array[2] = Europe
Я хотел бы использовать простой код, скорость команды не имеет значения. Как мне это сделать?
,
(запятую), а не один символ, такой как запятая. Если вас интересует только последнее, ответы здесь проще найти: stackoverflow.com/questions/918886/…cut
полезно иметь в виду команду bash. Разделитель можно задать en.wikibooks.org/wiki/Cut Вы также можете извлечь данные из структуры записи фиксированной ширины. en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmОтветы:
Обратите внимание , что символы в
$IFS
обрабатываются по отдельности в качестве разделителей , так что в данном случае поля могут быть разделены либо запятой или пробелом , а не последовательности из двух символов. Интересно, что пустые поля не создаются, когда во входе появляется запятая, потому что пространство обрабатывается специально.Чтобы получить доступ к отдельному элементу:
Чтобы перебрать элементы:
Чтобы получить индекс и значение:
Последний пример полезен, потому что массивы Bash редки. Другими словами, вы можете удалить элемент или добавить элемент, и тогда индексы не будут смежными.
Чтобы получить количество элементов в массиве:
Как упомянуто выше, массивы могут быть разреженными, поэтому вы не должны использовать длину, чтобы получить последний элемент. Вот как вы можете это сделать в Bash 4.2 и позже:
в любой версии Bash (откуда-то после 2.05b):
Большие отрицательные смещения выбираются дальше от конца массива. Обратите внимание на пробел перед знаком минус в старшей форме. Требуется.
источник
IFS=', '
, тогда вам не нужно удалять пробелы отдельно. Тест:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
Кстати, мне нравится использовать для тестового вывода.France, Europe, "Congo, The Democratic Republic of the"
это будет разделяться после Конго.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
будет разделен наarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
записку. Так что это работает только с полями без пробелов, посколькуIFS=', '
это набор отдельных символов, а не разделитель строк.Все ответы на этот вопрос так или иначе неверны.
Неправильный ответ № 1
1: Это неправильное использование
$IFS
. Значение$IFS
переменной не принимается как одиночный строковый разделитель переменной длины , скорее оно принимается как набор строковых разделителей из одного символа , где каждое поле, котороеread
отделяется от входной строки, может заканчиваться любым символом в наборе (запятая или пробел, в этом примере).На самом деле, для настоящих приверженцев, полное значение
$IFS
немного сложнее. Из руководства по bash :По сути, для ненулевых значений по умолчанию, отличных от NULL
$IFS
, поля могут быть отделены либо (1) последовательностью из одного или нескольких символов, которые все находятся в наборе «пробельных символов IFS» (то есть, в зависимости от <space> , <tab> и <newline> («новая строка », означающая перевод строки (LF) ) присутствуют где-либо в$IFS
), или (2) любой не «символ пробела IFS», который присутствует$IFS
вместе со всеми «символами пробела IFS», окружающими его в строке ввода.Для OP возможно, что второй режим разделения, который я описал в предыдущем параграфе, именно то, что он хочет для своей входной строки, но мы можем быть достаточно уверены, что первый режим разделения, который я описал, совсем не корректен. Например, что если его входная строка была
'Los Angeles, United States, North America'
?2: Даже если бы вы использовали это решение с односимвольным разделителем (например, запятой отдельно, то есть без пробела или другого багажа), если значение
$string
переменной, как оказалось, содержит какие-либо LF, тоread
будет остановите обработку, как только он встретит первый LF.read
Встроенный обрабатывает только одну строку на вызов. Это верно, даже если вы передаете или перенаправляете ввод только вread
оператор, как мы делаем в этом примере с механизмом здесь-строки , и, следовательно, необработанный ввод гарантированно будет потерян. Код, обеспечивающий работуread
встроенного модуля, не знает о потоке данных в его структуре команд.Вы можете утверждать, что это вряд ли вызовет проблему, но, тем не менее, это скрытая опасность, которую следует избегать, если это возможно. Это связано с тем, что
read
встроенный модуль фактически выполняет два уровня разбиения ввода: сначала на строки, а затем на поля. Поскольку OP требует только одного уровня разбиения, такое использованиеread
встроенной функции не подходит, и мы должны избегать этого.3: Неочевидная потенциальная проблема с этим решением состоит в том, что
read
всегда удаляет завершающее поле, если оно пустое, хотя в противном случае оно сохраняет пустые поля. Вот демо:Может быть, ОП не заботится об этом, но об этом стоит знать. Это снижает надежность и универсальность решения.
Эту проблему можно решить, добавив фиктивный конечный разделитель к входной строке непосредственно перед ее передачей
read
, как я продемонстрирую позже.Неправильный ответ № 2
Похожая идея:
(Примечание: я добавил пропущенные скобки вокруг подстановки команд, которые, по-видимому, опрошенный пропустил.)
Похожая идея:
Эти решения используют разделение слов в присваивании массива для разделения строки на поля. Как ни странно, как и при
read
общем разделении слов, также используется$IFS
специальная переменная, хотя в этом случае подразумевается, что для нее установлено значение по умолчанию <space> <tab> <newline> и, следовательно, любая последовательность из одного или нескольких IFS. символы (которые теперь являются символами пробелов) считаются разделителем полей.Это решает проблему двух уровней разделения, совершаемых
read
, поскольку разделение слов само по себе составляет только один уровень разделения. Но, как и прежде, проблема заключается в том, что отдельные поля во входной строке уже могут содержать$IFS
символы, и, таким образом, они будут неправильно разделены во время операции разделения слов. Это не относится ни к одному из примеров входных строк, предоставленных этими ответчиками (насколько это удобно ...), но, конечно, это не меняет того факта, что любая кодовая база, которая использовала эту идиому, в таком случае рискует взрыва, если это предположение когда-либо нарушалось в какой-то момент по линии. Еще раз рассмотрим мой контрпример'Los Angeles, United States, North America'
(или'Los Angeles:United States:North America'
).Кроме того, за разделением слов обычно следует расширение имени файла ( иначе имен файлов ака подстановки), который, если сделана, потенциально коррумпированные слова , содержащие символы
*
,?
или[
следует]
(и, еслиextglob
установлен, Скобки фрагменты предшествуют?
,*
,+
,@
, или!
) сопоставляя их с объектами файловой системы и расширяя слова ("globs") соответственно. Первый из этих трех ответчиков ловко подправил эту проблему, запустивset -f
заранее, чтобы отключить сглаживание. Технически это работает (хотя вы, вероятно, должны добавитьset +f
после этого можно повторно включить глобализацию для последующего кода, который может зависеть от него), но нежелательно возиться с глобальными настройками оболочки, чтобы взломать базовую операцию анализа строки в массив в локальном коде.Другая проблема с этим ответом состоит в том, что все пустые поля будут потеряны. Это может или не может быть проблемой, в зависимости от приложения.
Примечание: если вы собираетесь использовать это решение, лучше использовать
${string//:/ }
форму расширения параметра «подстановка шаблона» , а не вызывать проблему подстановки команды (которая создает оболочку), запуска конвейера и запуск внешнего исполняемого файла (tr
илиsed
), поскольку расширение параметра является чисто внутренней операцией. (Кроме того , дляtr
иsed
решений, входная переменная должна быть в двойных кавычках внутри подстановки команд, в противном случае слово расщепления вступит в силу вecho
команде и потенциально путаницы со значениями поля Также.$(...)
Форма подстановки команд предпочтительнее старый`...`
формы, поскольку она упрощает вложение подстановок команд и позволяет лучше выделять синтаксис текстовыми редакторами.)Неправильный ответ № 3
Этот ответ почти такой же, как № 2 . Разница в том, что ответчик сделал предположение, что поля разделены двумя символами, один из которых представлен по умолчанию
$IFS
, а другой нет. Он решил этот довольно специфический случай, удалив не-IFS-представленный символ, используя расширение подстановки шаблонов, а затем используя разделение слов, чтобы разделить поля на оставшемся IFS-представленном символе-разделителе.Это не очень общее решение. Кроме того, можно утверждать, что запятая на самом деле является «основным» символом разделителя, и что ее удаление и последующее использование символа пробела для разделения поля просто неверно. Еще раз рассмотрим мои контрпример:
'Los Angeles, United States, North America'
.Также, опять же, расширение имени файла может повредить расширенные слова, но это можно предотвратить, временно отключив глобализацию для назначения с помощью
set -f
и затемset +f
.Кроме того, опять все пустые поля будут потеряны, что может быть или не быть проблемой в зависимости от приложения.
Неправильный ответ № 4
Это похоже на № 2 и № 3 в том, что для выполнения работы используется разделение слов, только теперь код явно устанавливает
$IFS
для того, чтобы он содержал только односимвольный разделитель полей, присутствующий во входной строке. Следует повторить, что это не может работать с разделителями полей из нескольких символов, такими как разделитель запятой в OP. Но для односимвольного разделителя, такого как LF, использованного в этом примере, он фактически близок к идеальному. Поля не могут быть непреднамеренно разделены посередине, как мы видели в предыдущих неправильных ответах, и при необходимости существует только один уровень разделения.Одна проблема состоит в том, что расширение имени файла повредит затронутые слова, как описано ранее, хотя еще раз это можно решить, заключив критическое утверждение в
set -f
иset +f
.Другая потенциальная проблема заключается в том, что, поскольку LF квалифицируется как «символ пробела IFS», как определено ранее, все пустые поля будут потеряны, как в # 2 и # 3 . Это, конечно, не будет проблемой, если разделитель окажется не «символом пробела IFS», и в зависимости от приложения это может не иметь значения в любом случае, но он нарушает универсальность решения.
Итак, если подвести итог, предположим, что у вас есть односимвольный разделитель, и он либо не является «символом пробела IFS», либо вас не волнуют пустые поля, и вы заключаете критический оператор в
set -f
иset +f
, тогда это решение работает , но в противном случае нет.(Кроме того, ради информации, назначение LF переменной в bash может быть сделано проще с помощью
$'...'
синтаксиса, напримерIFS=$'\n';
.)Неправильный ответ № 5
Похожая идея:
Это решение фактически представляет собой нечто среднее между # 1 (в том смысле, что он устанавливает
$IFS
запятую) и # 2-4 (в том смысле, что для разбиения строки на поля используется разбиение слов). Из-за этого он страдает от большинства проблем, которые затрагивают все вышеупомянутые неправильные ответы, вроде как худший из всех миров.Также, что касается второго варианта, может показаться, что
eval
вызов совершенно не нужен, так как его аргумент является строковым литералом в одинарных кавычках и поэтому является статически известным. Но на самом деле есть очень неочевидное преимущество использованияeval
таким способом. Обычно, когда вы запускаете команду простой , который состоит из присвоения переменной только , то есть без фактического командного слова после него, назначение вступает в силу в среде оболочки:Это верно, даже если простая команда включает в себя несколько назначений переменных; опять же, пока нет командного слова, все назначения переменных влияют на среду оболочки:
Но, если присвоение переменной присоединено к имени команды (мне нравится называть это «назначением префикса»), то это не влияет на среду оболочки, а вместо этого влияет только на среду исполняемой команды, независимо от того, является ли она встроенной. или внешний:
Соответствующая цитата из руководства по bash :
Эту особенность назначения переменных можно использовать
$IFS
только для временного изменения , что позволяет нам избежать всего гамбита сохранения и восстановления, подобного тому, что делается с$OIFS
переменной в первом варианте. Но проблема, с которой мы здесь сталкиваемся, заключается в том, что команда, которую мы должны выполнить, сама по себе является простым присвоением переменной, и, следовательно, она не будет включать командное слово, чтобы сделать$IFS
назначение временным. Вы можете подумать про себя: ну почему бы просто не добавить командное слово no-op в оператор, например,: builtin
чтобы сделать$IFS
назначение временным? Это не работает, потому что это сделало бы$array
назначение также временным:Таким образом, мы находимся в тупике, что-то вроде ловушки-22. Но когда он
eval
запускает свой код, он запускает его в среде оболочки, как если бы это был обычный статический исходный код, и поэтому мы можем запустить$array
присвоение внутриeval
аргумента, чтобы оно вступило в силу в среде оболочки, тогда как$IFS
присвоение префикса, которое префикс кeval
команде не переживетeval
команду. Это именно та хитрость, которая используется во втором варианте этого решения:Итак, как вы можете видеть, это на самом деле довольно умный трюк, и он выполняет именно то, что требуется (по крайней мере, в отношении выполнения присваивания), довольно неочевидным способом. Я на самом деле не против этого трюка в целом, несмотря на участие
eval
; просто будьте осторожны, чтобы заключить строку аргумента в одну кавычку для защиты от угроз безопасности.Но опять же, из-за наихудшей агломерации проблем, это все еще неправильный ответ на требование ФП.
Неправильный ответ № 6
Гм ... что? У OP есть строковая переменная, которую нужно проанализировать в массив. Этот «ответ» начинается с дословного содержимого входной строки, вставленной в литерал массива. Я думаю, это один из способов сделать это.
Похоже, что ответчик мог предположить, что эта
$IFS
переменная влияет на любой синтаксический анализ bash во всех контекстах, что неверно. Из руководства по bash:Таким образом,
$IFS
специальная переменная фактически используется только в двух контекстах: (1) разбиение слов, которое выполняется после раскрытия (то есть не при разборе исходного кода bash) и (2) для разбиения входных строк на словаread
встроенным.Позвольте мне попытаться прояснить это. Я думаю, что было бы хорошо провести различие между разбором и выполнением . Bash должен сначала проанализировать исходный код, который, очевидно, является событием синтаксического анализа , а затем позже он выполняет код, когда происходит расширение. Расширение действительно является событием исполнения . Кроме того, я не согласен с описанием
$IFS
переменной, которую я только что цитировал; Вместо того, чтобы говорить, что разделение слов выполняется после раскрытия , я бы сказал, что разделение слов выполняется во время раскрытия, или, возможно, даже более точно, разделение слов частьюпроцесс расширения. Фраза «расщепление слов» относится только к этому этапу расширения; его никогда не следует использовать для ссылки на синтаксический анализ исходного кода bash, хотя, к сожалению, документы, похоже, содержат много слов «split» и «words». Вот соответствующая выдержка из linux.die.net версии руководства по bash:Можно утверждать, что версия руководства для GNU работает немного лучше, поскольку в первом предложении раздела «Расширение» выбрано слово «токены» вместо «слова»:
Важным моментом является то,
$IFS
что bash не изменяет способ анализа исходного кода. Разбор исходного кода bash на самом деле является очень сложным процессом, который включает в себя распознавание различных элементов грамматики оболочки, таких как последовательности команд, списки команд, конвейеры, раскрытия параметров, арифметические замены и замены команд. По большей части процесс синтаксического анализа bash не может быть изменен действиями пользовательского уровня, такими как присвоение переменных (на самом деле, есть некоторые незначительные исключения из этого правила; например, посмотрите различныеcompatxx
параметры оболочки, что может изменить некоторые аспекты синтаксического анализа на лету). Вышеупомянутые «слова» / «токены», которые возникают в результате этого сложного процесса синтаксического анализа, затем расширяются в соответствии с общим процессом «расширения», как разбито в приведенных выше отрывках документации, где разбиение слов расширенного (расширяющегося?) Текста на нисходящий слова это просто один из шагов этого процесса. Разделение слов касается только текста, выпавшего из предыдущего шага расширения; это не влияет на буквальный текст, который был проанализирован сразу же по исходному потоку.Неправильный ответ № 7
Это одно из лучших решений. Обратите внимание, что мы вернулись к использованию
read
. Разве я не говорил ранее, чтоread
это неуместно, потому что он выполняет два уровня разделения, когда нам нужен только один? Хитрость заключается в том, что вы можете вызыватьread
таким образом, чтобы он эффективно выполнял только один уровень разделения, в частности, путем разделения только одного поля на вызов, что требует затрат на его повторный вызов в цикле. Это немного ловкость рук, но это работает.Но есть проблемы. Во-первых: когда вы предоставляете хотя бы один аргумент NAME
read
, он автоматически игнорирует начальные и конечные пробелы в каждом поле, которое отделено от входной строки. Это происходит независимо от того$IFS
, установлено ли его значение по умолчанию или нет, как описано ранее в этом посте. Теперь OP может не заботиться об этом для своего конкретного варианта использования, и на самом деле, это может быть желательной особенностью поведения синтаксического анализа. Но не каждый, кто хочет разобрать строку в полях, захочет этого. Однако есть решение: несколько неочевидное использованиеread
- передать ноль аргументов NAME . В этом случаеread
будет храниться вся входная строка, полученная из входного потока, в переменной с именем$REPLY
, и, в качестве бонуса, она не будетуберите начальные и конечные пробелы из значения. Это очень надежное использование,read
которое я часто использовал в своей карьере программиста оболочки. Вот демонстрация различий в поведении:Вторая проблема, связанная с этим решением, заключается в том, что в нем фактически не рассматривается случай разделителя пользовательских полей, например запятой OP. Как и прежде, разделители с несколькими символами не поддерживаются, что является нежелательным ограничением этого решения. Мы могли бы попытаться хотя бы разделить запятую, указав разделитель для
-d
опции, но посмотрим, что произойдет:Как и ожидалось, неучтенные окружающие пробелы были включены в значения полей, и, следовательно, это необходимо было бы впоследствии исправить с помощью операций обрезки (это также можно сделать непосредственно в цикле while). Но есть еще одна очевидная ошибка: Европа отсутствует! Что случилось с этим? Ответ заключается в том, что
read
возвращает ошибочный код возврата, если он достигает конца файла (в этом случае мы можем назвать его концом строки), не встретив завершающий терминатор поля в последнем поле. Это приводит к преждевременному разрыву цикла while, и мы теряем последнее поле.Технически эта же ошибка затронула и предыдущие примеры; разница в том, что разделитель полей был выбран как LF, который используется по умолчанию, когда вы не указываете
-d
опцию, и<<<
механизм ("here-string") автоматически добавляет LF к строке непосредственно перед тем, как она передает ее как ввод в команду. Следовательно, в этих случаях мы как бы случайно решили проблему пропущенного конечного поля, невольно добавив дополнительный фиктивный терминатор к входу. Давайте назовем это решение решением "фиктивного терминатора". Мы можем применить решение dummy-terminator вручную для любого пользовательского разделителя, сцепив его с входной строкой самостоятельно, когда создаем его экземпляр в строке here:Там проблема решена. Другое решение состоит в том, чтобы прерывать цикл while только в том случае, если оба (1)
read
вернули сбой и (2)$REPLY
пусто, то естьread
не смогли прочитать ни одного символа до попадания в конец файла. Демо - версия:Этот подход также раскрывает скрытую LF, которая автоматически добавляется к строке здесь
<<<
оператором перенаправления. Конечно, его можно удалить отдельно с помощью явной операции обрезки, как описано минуту назад, но очевидно, что ручной подход к фиктивному терминатору решает это напрямую, поэтому мы могли бы просто пойти на это. Ручное решение для фиктивного терминатора на самом деле весьма удобно, поскольку оно решает обе эти проблемы (проблему опущенного конечного поля и проблему добавленной НЧ) за один раз.В общем, это довольно мощное решение. Единственный недостаток - отсутствие поддержки разделителей из нескольких символов, о которых я расскажу позже.
Неправильный ответ № 8
(Это на самом деле из того же поста, что и №7 ; ответчик предоставил два решения в одном и том же посте.)
readarray
Встроенный, который является синонимомmapfile
, является идеальным. Это встроенная команда, которая анализирует поток байтов в переменную массива за один раз; не возиться с циклами, условными выражениями, подстановками или чем-либо еще. И это не скрыто убирает пробелы из входной строки. И (если-O
не указан) он очищает целевой массив перед его назначением. Но это все еще не идеально, поэтому я критикую это как «неправильный ответ».Во-первых, просто чтобы убрать это с пути, обратите внимание, что, подобно поведению
read
при разборе поля,readarray
удаляется завершающее поле, если оно пустое. Опять же, это, вероятно, не проблема для OP, но это может быть для некоторых вариантов использования. Я вернусь к этому через минуту.Во-вторых, как и прежде, он не поддерживает разделители с несколькими символами. Я исправлю это через мгновение.
В-третьих, написанное решение не анализирует входную строку OP и фактически не может использоваться для анализа как есть. Я также подробно остановлюсь на этом.
По вышеуказанным причинам я по-прежнему считаю это «неправильным ответом» на вопрос ОП. Ниже я приведу то, что считаю правильным ответом.
Правильный ответ
Вот наивная попытка заставить # 8 работать, просто указав
-d
параметр:Мы видим, что результат идентичен результату, который мы получили благодаря двойному условию циклического
read
решения, которое обсуждалось в # 7 . Мы можем почти решить эту проблему с помощью ручного трюка-заглушки:Проблема здесь в том, что
readarray
сохраняется конечное поле, поскольку<<<
оператор перенаправления добавляет LF к входной строке, и поэтому конечное поле не было пустым (в противном случае оно было бы удалено). Мы можем позаботиться об этом, явно сбросив окончательный элемент массива:Остались только две проблемы, которые на самом деле связаны между собой: (1) посторонние пробелы, которые необходимо обрезать, и (2) отсутствие поддержки разделителей из нескольких символов.
Пробельные символы, конечно, могут быть обрезаны позже (например, см. Как обрезать пустые места из переменной Bash? ). Но если мы сможем взломать разделитель из нескольких символов, то это решит обе проблемы за один раз.
К сожалению, нет прямого способа заставить работать разделитель из нескольких символов. Лучшее решение, о котором я подумал, - это предварительная обработка входной строки для замены разделителя из нескольких символов на символьный разделитель, который гарантированно не будет конфликтовать с содержимым входной строки. Единственный символ, имеющий эту гарантию, - это байт NUL . Это связано с тем, что в bash (хотя, впрочем, и не в zsh) переменные не могут содержать байт NUL. Этот шаг предварительной обработки может быть выполнен в процессе подстановки процесса. Вот как это сделать с помощью awk :
Там наконец-то! Это решение не будет ошибочно разделять поля посередине, не будет преждевременно вырезаться, не будет сбрасывать пустые поля, не будет повреждаться при расширении имени файла, не будет автоматически убирать начальные и конечные пробелы, не будет оставлять промежуточный LF на конце, не требует циклов и не соглашается с разделителем из одного символа.
Решение для обрезки
Наконец, я хотел продемонстрировать свое собственное довольно сложное решение для обрезки, используя неясную
-C callback
опциюreadarray
. К сожалению, мне не хватает места для драконовского предела в 30 000 символов в Stack Overflow, поэтому я не смогу это объяснить. Я оставлю это как упражнение для читателя.источник
-d
опцияreadarray
сначала появляется в Bash 4.4.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
и удалите это объединение финала,", "
то вам не нужно проходить гимнастику для устранения финальной записи. Итак:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
на Bash это поддерживаетreadarray
. Обратите внимание , что ваш метод является Bash 4.4 и выше я думаю , что из-за-d
вreadarray
readarray
. В этом случае вы можете использовать второе лучшее решение на основеread
. Я имею в виду это:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(сawk
заменой, если вам нужна поддержка разделителя нескольких символов). Дайте мне знать, если у вас возникнут какие-либо проблемы; Я уверен, что это решение должно работать на довольно старых версиях bash, начиная с версии 2, выпущенной два десятилетия назад.Вот способ без настройки IFS:
Идея заключается в использовании замены строки:
заменить все совпадения $ substring пробелами, а затем использовать замещенную строку для инициализации массива:
Примечание: в этом ответе используется оператор split + glob . Таким образом, чтобы предотвратить расширение некоторых символов (таких как
*
), рекомендуется приостановить глобализацию для этого сценария.источник
${string//:/ }
предотвращает расширение оболочкиarray=(${string//:/ })
Печатает три
источник
a=($(echo $t | tr ',' "\n"))
. Тот же результат сa=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
вbash
раковине, и последнийecho
раз печатает пустую строку. Какую версию Linux и какую оболочку вы используете? К сожалению, не может отобразить сеанс терминала в комментарии.Иногда мне случалось так, что метод, описанный в принятом ответе, не работал, особенно если разделитель - возврат каретки.
В этих случаях я решил следующим образом:
источник
read -a arr <<< "$strings"
не работал сIFS=$'\n'
.Принятый ответ работает для значений в одну строку.
Если переменная имеет несколько строк:
Нам нужна совсем другая команда, чтобы получить все строки:
while read -r line; do lines+=("$line"); done <<<"$string"
Или намного более простой readarray bash :
Печать всех строк очень проста с использованием функции printf:
источник
Это похоже на подход Jmoney38 , но с использованием sed:
Принты 1
источник
Ключом к разбиению вашей строки на массив является многосимвольный разделитель
", "
. Любое решение с использованиемIFS
многосимвольные разделители, по своей сути неверно, поскольку IFS - это набор этих символов, а не строка.Если вы назначите,
IFS=", "
то строка будет разбиваться на","
ИЛИ ИЛИ" "
или на любую их комбинацию, которая не является точным представлением двухсимвольного разделителя", "
.Вы можете использовать
awk
илиsed
для разделения строки, с подстановкой процесса:Более эффективно использовать регулярные выражения непосредственно в Bash:
Со второй формой нет вложенной оболочки, и она будет по своей природе быстрее.
Редактирование bgoldst: Вот некоторые тесты, сравнивающие мое
readarray
решение с решением dawg для регулярных выражений, и я также включилread
решение для его проверки (примечание: я немного изменил решение regex для большей гармонии с моим решением) (также см. Мои комментарии ниже после):источник
$BASH_REMATCH
. Это работает, и действительно избегает порождения подоболочек. +1 от меня. Однако, в порядке критики, само регулярное выражение немного неидеально, так как кажется, что вы были вынуждены дублировать часть токена-разделителя (в частности, запятую), чтобы обойти отсутствие поддержки не жадных множителей (также lookarounds) в ERE («расширенный» вкус регулярных выражений, встроенный в bash). Это делает его немного менее универсальным и надежным.\n
текстовые строки с разделителями), включающая эти поля, так что катастрофическое замедление, скорее всего, не произойдет. Если у вас есть строка с 100 000 полей - возможно, Bash не идеален ;-) Спасибо за тест. Я узнал одну или две вещи.Решение Pure Bash для многосимвольных разделителей.
Как уже указывали другие в этой теме, вопрос OP привел пример строки с разделителями-запятыми, которая должна быть проанализирована в массиве, но не указала, интересовался ли он / она только разделителями-запятыми, разделителями из одного символа или многосимвольными разделители.
Поскольку Google имеет тенденцию оценивать этот ответ в верхней части результатов поиска или рядом с ней, я хотел дать читателям четкий ответ на вопрос о разделителях из нескольких символов, поскольку он также упоминается по крайней мере в одном ответе.
Если вы ищете решение проблемы многосимвольного разделителя, я предлагаю рассмотреть сообщение Малликарджуна М , в частности ответ от gniourf_gniourf, который предоставляет это элегантное чистое решение BASH, используя расширение параметров:
Ссылка на цитируемый комментарий / ссылочную запись
Ссылка на процитированный вопрос: Как разбить строку на многосимвольном разделителе в bash?
источник
Это работает для меня на OSX:
Если ваша строка имеет другой разделитель, просто 1-й замените их пробелом:
Просто :-)
источник
Еще один способ сделать это без изменения IFS:
Вместо изменения IFS в соответствии с желаемым разделителем, мы можем заменить все вхождения желаемого разделителя
", "
содержимым$IFS
via"${string//, /$IFS}"
.Может быть, это будет медленно для очень больших строк, хотя?
Это основано на ответе Денниса Уильямсона.
источник
Я наткнулся на этот пост, когда хотел разобрать входные данные, такие как: word1, word2, ...
ничто из перечисленного не помогло мне. решил это с помощью awk. Если это кому-то поможет:
источник
Попробуй это
Это просто. Если вы хотите, вы также можете добавить объявление (и также удалить запятые):
IFS добавлен, чтобы отменить вышеупомянутое, но это работает без этого в новом случае bash
источник
Мы можем использовать команду tr для разделения строки на объект массива. Работает как MacOS, так и Linux
Другой вариант использовать команду IFS
источник
Использовать этот:
источник
array=( $string )
это ( к сожалению , очень часто) антипаттерн: слово происходит расщепление:string='Prague, Czech Republic, Europe'
; Происходит раскрытие пути:string='foo[abcd],bar[efgh]'
произойдет сбой, если у вас есть файл с именем, например,food
илиbarf
в вашем каталоге. Единственное допустимое использование такой конструкции - когдаstring
это глоб.ОБНОВЛЕНИЕ: не делайте этого из-за проблем с eval.
С немного меньшей церемонией:
например
источник
$
переменную в свою переменную, и вы увидите ... Я пишу много скриптов, и мне никогда не приходилось использовать одинeval
Вот мой хак!
Разделять строки по строкам довольно скучно при использовании bash. Что происходит, так это то, что у нас ограниченные подходы, которые работают только в нескольких случаях (разделенных на «;», «/», «.» И т. Д.) Или у нас есть множество побочных эффектов в выходных данных.
Приведенный ниже подход потребовал ряда маневров, но я считаю, что он будет работать для большинства наших потребностей!
источник
Для многослойных элементов, почему бы не что-то вроде
источник
Другой способ будет:
Теперь ваши элементы хранятся в массиве "arr". Чтобы перебрать элементы:
источник
eval
уловки). Ваше решение оставляет$IFS
значение запятой после фактического значения.Поскольку существует множество способов решения этой проблемы, давайте начнем с определения того, что мы хотим видеть в нашем решении.
readarray
для этой цели. Давайте использовать это.IFS
, зацикливание, использованиеeval
или добавление дополнительного элемента, а затем его удаление.readarray
Команда простая в использовании с новой строкой в качестве разделителя. С другими разделителями это может добавить дополнительный элемент в массив. Самый чистый подход заключается в том, чтобы сначала адаптировать наш ввод в форму, которая хорошо работает сreadarray
он прежде чем передать его.Входные данные в этом примере не имеют разделителя из нескольких символов. Если мы применяем немного здравого смысла, лучше всего понимать его как разделенный запятыми ввод, для которого каждый элемент может потребоваться обрезать. Мое решение состоит в том, чтобы разделить ввод через запятую на несколько строк, обрезать каждый элемент и передать все это
readarray
.источник
Другой подход может быть:
После этого 'arr' - массив с четырьмя строками. Это не требует обращения к IFS, чтению или другим специальным материалам, а значит, намного проще и понятнее.
источник