Все ответы на этот вопрос так или иначе неверны.
Неправильный ответ № 1
IFS=', ' read -r -a array <<< "$string"
1: Это неправильное использование $IFS
. Значение $IFS
переменной не принимается как одиночный строковый разделитель переменной длины , скорее оно принимается как набор строковых разделителей из одного символа , где каждое поле, которое read
отделяется от входной строки, может заканчиваться любым символом в наборе (запятая или пробел, в этом примере).
На самом деле, для настоящих приверженцев, полное значение $IFS
немного сложнее. Из руководства по bash :
Оболочка обрабатывает каждый символ IFS как разделитель и разбивает результаты других расширений на слова, используя эти символы в качестве разделителей полей. Если IFS не установлен или его значение в точности равно <пробел> <tab> <новая строка> , значение по умолчанию, тогда последовательности <пробел> , <tab> и <newline> в начале и конце результатов предыдущих расширений игнорируются, и любая последовательность символов IFS не в начале или в конце служит для разделения слов. Если IFS имеет значение, отличное от значения по умолчанию, то последовательности символов пробела <space> , <tab> и <игнорируются в начале и конце слова, если символ пробела находится в значении IFS ( символ пробела IFS ). Любой символ в IFS, который не является пробелом IFS , вместе с любыми соседними символами пробела IFS разделяет поле. Последовательность пробельных символов IFS также рассматривается как разделитель. Если значение IFS равно нулю, разделение слов не происходит.
По сути, для ненулевых значений по умолчанию, отличных от NULL $IFS
, поля могут быть отделены либо (1) последовательностью из одного или нескольких символов, которые все находятся в наборе «пробельных символов IFS» (то есть, в зависимости от <space> , <tab> и <newline> («новая строка », означающая перевод строки (LF) ) присутствуют где-либо в $IFS
), или (2) любой не «символ пробела IFS», который присутствует $IFS
вместе со всеми «символами пробела IFS», окружающими его в строке ввода.
Для OP возможно, что второй режим разделения, который я описал в предыдущем параграфе, именно то, что он хочет для своей входной строки, но мы можем быть достаточно уверены, что первый режим разделения, который я описал, совсем не корректен. Например, что если его входная строка была 'Los Angeles, United States, North America'
?
IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")
2: Даже если бы вы использовали это решение с односимвольным разделителем (например, запятой отдельно, то есть без пробела или другого багажа), если значение $string
переменной, как оказалось, содержит какие-либо LF, то read
будет остановите обработку, как только он встретит первый LF. read
Встроенный обрабатывает только одну строку на вызов. Это верно, даже если вы передаете или перенаправляете ввод только в read
оператор, как мы делаем в этом примере с механизмом здесь-строки , и, следовательно, необработанный ввод гарантированно будет потерян. Код, обеспечивающий работу read
встроенного модуля, не знает о потоке данных в его структуре команд.
Вы можете утверждать, что это вряд ли вызовет проблему, но, тем не менее, это скрытая опасность, которую следует избегать, если это возможно. Это связано с тем, что read
встроенный модуль фактически выполняет два уровня разбиения ввода: сначала на строки, а затем на поля. Поскольку OP требует только одного уровня разбиения, такое использование read
встроенной функции не подходит, и мы должны избегать этого.
3: Неочевидная потенциальная проблема с этим решением состоит в том, что read
всегда удаляет завершающее поле, если оно пустое, хотя в противном случае оно сохраняет пустые поля. Вот демо:
string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")
Может быть, ОП не заботится об этом, но об этом стоит знать. Это снижает надежность и универсальность решения.
Эту проблему можно решить, добавив фиктивный конечный разделитель к входной строке непосредственно перед ее передачей read
, как я продемонстрирую позже.
Неправильный ответ № 2
string="1:2:3:4:5"
set -f # avoid globbing (expansion of *).
array=(${string//:/ })
Похожая идея:
t="one,two,three"
a=($(echo $t | tr ',' "\n"))
(Примечание: я добавил пропущенные скобки вокруг подстановки команд, которые, по-видимому, опрошенный пропустил.)
Похожая идея:
string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
Эти решения используют разделение слов в присваивании массива для разделения строки на поля. Как ни странно, как и при 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
str="a, b, c, d" # assuming there is a space after ',' as in Q
arr=(${str//,/}) # delete all occurrences of ','
Этот ответ почти такой же, как № 2 . Разница в том, что ответчик сделал предположение, что поля разделены двумя символами, один из которых представлен по умолчанию $IFS
, а другой нет. Он решил этот довольно специфический случай, удалив не-IFS-представленный символ, используя расширение подстановки шаблонов, а затем используя разделение слов, чтобы разделить поля на оставшемся IFS-представленном символе-разделителе.
Это не очень общее решение. Кроме того, можно утверждать, что запятая на самом деле является «основным» символом разделителя, и что ее удаление и последующее использование символа пробела для разделения поля просто неверно. Еще раз рассмотрим мои контрпример: 'Los Angeles, United States, North America'
.
Также, опять же, расширение имени файла может повредить расширенные слова, но это можно предотвратить, временно отключив глобализацию для назначения с помощью set -f
и затем set +f
.
Кроме того, опять все пустые поля будут потеряны, что может быть или не быть проблемой в зависимости от приложения.
Неправильный ответ № 4
string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"
Это похоже на № 2 и № 3 в том, что для выполнения работы используется разделение слов, только теперь код явно устанавливает $IFS
для того, чтобы он содержал только односимвольный разделитель полей, присутствующий во входной строке. Следует повторить, что это не может работать с разделителями полей из нескольких символов, такими как разделитель запятой в OP. Но для односимвольного разделителя, такого как LF, использованного в этом примере, он фактически близок к идеальному. Поля не могут быть непреднамеренно разделены посередине, как мы видели в предыдущих неправильных ответах, и при необходимости существует только один уровень разделения.
Одна проблема состоит в том, что расширение имени файла повредит затронутые слова, как описано ранее, хотя еще раз это можно решить, заключив критическое утверждение в set -f
иset +f
.
Другая потенциальная проблема заключается в том, что, поскольку LF квалифицируется как «символ пробела IFS», как определено ранее, все пустые поля будут потеряны, как в # 2 и # 3 . Это, конечно, не будет проблемой, если разделитель окажется не «символом пробела IFS», и в зависимости от приложения это может не иметь значения в любом случае, но он нарушает универсальность решения.
Итак, если подвести итог, предположим, что у вас есть односимвольный разделитель, и он либо не является «символом пробела IFS», либо вас не волнуют пустые поля, и вы заключаете критический оператор в set -f
и set +f
, тогда это решение работает , но в противном случае нет.
(Кроме того, ради информации, назначение LF переменной в bash может быть сделано проще с помощью $'...'
синтаксиса, например IFS=$'\n';
.)
Неправильный ответ № 5
countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"
Похожая идея:
IFS=', ' eval 'array=($string)'
Это решение фактически представляет собой нечто среднее между # 1 (в том смысле, что он устанавливает $IFS
запятую) и # 2-4 (в том смысле, что для разбиения строки на поля используется разбиение слов). Из-за этого он страдает от большинства проблем, которые затрагивают все вышеупомянутые неправильные ответы, вроде как худший из всех миров.
Также, что касается второго варианта, может показаться, что eval
вызов совершенно не нужен, так как его аргумент является строковым литералом в одинарных кавычках и поэтому является статически известным. Но на самом деле есть очень неочевидное преимущество использования eval
таким способом. Обычно, когда вы запускаете команду простой , который состоит из присвоения переменной только , то есть без фактического командного слова после него, назначение вступает в силу в среде оболочки:
IFS=', '; ## changes $IFS in the shell environment
Это верно, даже если простая команда включает в себя несколько назначений переменных; опять же, пока нет командного слова, все назначения переменных влияют на среду оболочки:
IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment
Но, если присвоение переменной присоединено к имени команды (мне нравится называть это «назначением префикса»), то это не влияет на среду оболочки, а вместо этого влияет только на среду исполняемой команды, независимо от того, является ли она встроенной. или внешний:
IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it
Соответствующая цитата из руководства по bash :
Если имя команды не найдено, назначение переменных влияет на текущую среду оболочки. В противном случае переменные добавляются в среду выполняемой команды и не влияют на текущую среду оболочки.
Эту особенность назначения переменных можно использовать $IFS
только для временного изменения , что позволяет нам избежать всего гамбита сохранения и восстановления, подобного тому, что делается с $OIFS
переменной в первом варианте. Но проблема, с которой мы здесь сталкиваемся, заключается в том, что команда, которую мы должны выполнить, сама по себе является простым присвоением переменной, и, следовательно, она не будет включать командное слово, чтобы сделать $IFS
назначение временным. Вы можете подумать про себя: ну почему бы просто не добавить командное слово no-op в оператор, например, : builtin
чтобы сделать $IFS
назначение временным? Это не работает, потому что это сделало бы $array
назначение также временным:
IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command
Таким образом, мы находимся в тупике, что-то вроде ловушки-22. Но когда он eval
запускает свой код, он запускает его в среде оболочки, как если бы это был обычный статический исходный код, и поэтому мы можем запустить $array
присвоение внутри eval
аргумента, чтобы оно вступило в силу в среде оболочки, тогда как $IFS
присвоение префикса, которое префикс к eval
команде не переживет eval
команду. Это именно та хитрость, которая используется во втором варианте этого решения:
IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does
Итак, как вы можете видеть, это на самом деле довольно умный трюк, и он выполняет именно то, что требуется (по крайней мере, в отношении выполнения присваивания), довольно неочевидным способом. Я на самом деле не против этого трюка в целом, несмотря на участие eval
; просто будьте осторожны, чтобы заключить строку аргумента в одну кавычку для защиты от угроз безопасности.
Но опять же, из-за наихудшей агломерации проблем, это все еще неправильный ответ на требование ФП.
Неправильный ответ № 6
IFS=', '; array=(Paris, France, Europe)
IFS=' ';declare -a array=(Paris France Europe)
Гм ... что? У OP есть строковая переменная, которую нужно проанализировать в массив. Этот «ответ» начинается с дословного содержимого входной строки, вставленной в литерал массива. Я думаю, это один из способов сделать это.
Похоже, что ответчик мог предположить, что эта $IFS
переменная влияет на любой синтаксический анализ bash во всех контекстах, что неверно. Из руководства по bash:
IFS Внутренний разделитель полей, который используется для разделения слов после раскрытия и разделения строк на слова с помощью встроенной команды read . Значением по умолчанию является <пробел> <вкладка> <новая строка> .
Таким образом, $IFS
специальная переменная фактически используется только в двух контекстах: (1) разбиение слов, которое выполняется после раскрытия (то есть не при разборе исходного кода bash) и (2) для разбиения входных строк на слова read
встроенным.
Позвольте мне попытаться прояснить это. Я думаю, что было бы хорошо провести различие между разбором и выполнением . Bash должен сначала проанализировать исходный код, который, очевидно, является событием синтаксического анализа , а затем позже он выполняет код, когда происходит расширение. Расширение действительно является событием исполнения . Кроме того, я не согласен с описанием $IFS
переменной, которую я только что цитировал; Вместо того, чтобы говорить, что разделение слов выполняется после раскрытия , я бы сказал, что разделение слов выполняется во время раскрытия, или, возможно, даже более точно, разделение слов частьюпроцесс расширения. Фраза «расщепление слов» относится только к этому этапу расширения; его никогда не следует использовать для ссылки на синтаксический анализ исходного кода bash, хотя, к сожалению, документы, похоже, содержат много слов «split» и «words». Вот соответствующая выдержка из linux.die.net версии руководства по bash:
Расширение выполняется в командной строке после того, как оно было разбито на слова. Есть семь видов расширения выполняется: в фигурных скобках , тильды , параметров и переменных расширения , подстановки команд , арифметическое расширение , слово расщепления и расширения имен файлов .
Порядок разложений: раскладывание скобок; раскрытие тильды, расширение параметров и переменных, арифметическое расширение и подстановка команд (выполняется слева направо); расщепление слов; и расширение пути.
Можно утверждать, что версия руководства для GNU работает немного лучше, поскольку в первом предложении раздела «Расширение» выбрано слово «токены» вместо «слова»:
Расширение выполняется в командной строке после его разбиения на токены.
Важным моментом является то, $IFS
что bash не изменяет способ анализа исходного кода. Разбор исходного кода bash на самом деле является очень сложным процессом, который включает в себя распознавание различных элементов грамматики оболочки, таких как последовательности команд, списки команд, конвейеры, раскрытия параметров, арифметические замены и замены команд. По большей части процесс синтаксического анализа bash не может быть изменен действиями пользовательского уровня, такими как присвоение переменных (на самом деле, есть некоторые незначительные исключения из этого правила; например, посмотрите различные compatxx
параметры оболочки, что может изменить некоторые аспекты синтаксического анализа на лету). Вышеупомянутые «слова» / «токены», которые возникают в результате этого сложного процесса синтаксического анализа, затем расширяются в соответствии с общим процессом «расширения», как разбито в приведенных выше отрывках документации, где разбиение слов расширенного (расширяющегося?) Текста на нисходящий слова это просто один из шагов этого процесса. Разделение слов касается только текста, выпавшего из предыдущего шага расширения; это не влияет на буквальный текст, который был проанализирован сразу же по исходному потоку.
Неправильный ответ № 7
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
Это одно из лучших решений. Обратите внимание, что мы вернулись к использованию read
. Разве я не говорил ранее, что read
это неуместно, потому что он выполняет два уровня разделения, когда нам нужен только один? Хитрость заключается в том, что вы можете вызывать read
таким образом, чтобы он эффективно выполнял только один уровень разделения, в частности, путем разделения только одного поля на вызов, что требует затрат на его повторный вызов в цикле. Это немного ловкость рук, но это работает.
Но есть проблемы. Во-первых: когда вы предоставляете хотя бы один аргумент NAMEread
, он автоматически игнорирует начальные и конечные пробелы в каждом поле, которое отделено от входной строки. Это происходит независимо от того $IFS
, установлено ли его значение по умолчанию или нет, как описано ранее в этом посте. Теперь OP может не заботиться об этом для своего конкретного варианта использования, и на самом деле, это может быть желательной особенностью поведения синтаксического анализа. Но не каждый, кто хочет разобрать строку в полях, захочет этого. Однако есть решение: несколько неочевидное использование read
- передать ноль аргументов NAME . В этом случае read
будет храниться вся входная строка, полученная из входного потока, в переменной с именем $REPLY
, и, в качестве бонуса, она не будетуберите начальные и конечные пробелы из значения. Это очень надежное использование, read
которое я часто использовал в своей карьере программиста оболочки. Вот демонстрация различий в поведении:
string=$' a b \n c d \n e f '; ## input string
a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a b" [1]="c d" [2]="e f") ## read trimmed surrounding whitespace
a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]=" a b " [1]=" c d " [2]=" e f ") ## no trimming
Вторая проблема, связанная с этим решением, заключается в том, что в нем фактически не рассматривается случай разделителя пользовательских полей, например запятой OP. Как и прежде, разделители с несколькими символами не поддерживаются, что является нежелательным ограничением этого решения. Мы могли бы попытаться хотя бы разделить запятую, указав разделитель для -d
опции, но посмотрим, что произойдет:
string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")
Как и ожидалось, неучтенные окружающие пробелы были включены в значения полей, и, следовательно, это необходимо было бы впоследствии исправить с помощью операций обрезки (это также можно сделать непосредственно в цикле while). Но есть еще одна очевидная ошибка: Европа отсутствует! Что случилось с этим? Ответ заключается в том, что read
возвращает ошибочный код возврата, если он достигает конца файла (в этом случае мы можем назвать его концом строки), не встретив завершающий терминатор поля в последнем поле. Это приводит к преждевременному разрыву цикла while, и мы теряем последнее поле.
Технически эта же ошибка затронула и предыдущие примеры; разница в том, что разделитель полей был выбран как LF, который используется по умолчанию, когда вы не указываете -d
опцию, и <<<
механизм ("here-string") автоматически добавляет LF к строке непосредственно перед тем, как она передает ее как ввод в команду. Следовательно, в этих случаях мы как бы случайно решили проблему пропущенного конечного поля, невольно добавив дополнительный фиктивный терминатор к входу. Давайте назовем это решение решением "фиктивного терминатора". Мы можем применить решение dummy-terminator вручную для любого пользовательского разделителя, сцепив его с входной строкой самостоятельно, когда создаем его экземпляр в строке here:
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
Там проблема решена. Другое решение состоит в том, чтобы прерывать цикл while только в том случае, если оба (1) read
вернули сбой и (2) $REPLY
пусто, то есть read
не смогли прочитать ни одного символа до попадания в конец файла. Демо - версия:
a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
Этот подход также раскрывает скрытую LF, которая автоматически добавляется к строке здесь <<<
оператором перенаправления. Конечно, его можно удалить отдельно с помощью явной операции обрезки, как описано минуту назад, но очевидно, что ручной подход к фиктивному терминатору решает это напрямую, поэтому мы могли бы просто пойти на это. Ручное решение для фиктивного терминатора на самом деле весьма удобно, поскольку оно решает обе эти проблемы (проблему опущенного конечного поля и проблему добавленной НЧ) за один раз.
В общем, это довольно мощное решение. Единственный недостаток - отсутствие поддержки разделителей из нескольких символов, о которых я расскажу позже.
Неправильный ответ № 8
string='first line
second line
third line'
readarray -t lines <<<"$string"
(Это на самом деле из того же поста, что и №7 ; ответчик предоставил два решения в одном и том же посте.)
readarray
Встроенный, который является синонимом mapfile
, является идеальным. Это встроенная команда, которая анализирует поток байтов в переменную массива за один раз; не возиться с циклами, условными выражениями, подстановками или чем-либо еще. И это не скрыто убирает пробелы из входной строки. И (если -O
не указан) он очищает целевой массив перед его назначением. Но это все еще не идеально, поэтому я критикую это как «неправильный ответ».
Во-первых, просто чтобы убрать это с пути, обратите внимание, что, подобно поведению read
при разборе поля, readarray
удаляется завершающее поле, если оно пустое. Опять же, это, вероятно, не проблема для OP, но это может быть для некоторых вариантов использования. Я вернусь к этому через минуту.
Во-вторых, как и прежде, он не поддерживает разделители с несколькими символами. Я исправлю это через мгновение.
В-третьих, написанное решение не анализирует входную строку OP и фактически не может использоваться для анализа как есть. Я также подробно остановлюсь на этом.
По вышеуказанным причинам я по-прежнему считаю это «неправильным ответом» на вопрос ОП. Ниже я приведу то, что считаю правильным ответом.
Правильный ответ
Вот наивная попытка заставить # 8 работать, просто указав -d
параметр:
string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
Мы видим, что результат идентичен результату, который мы получили благодаря двойному условию циклического read
решения, которое обсуждалось в # 7 . Мы можем почти решить эту проблему с помощью ручного трюка-заглушки:
readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')
Проблема здесь в том, что readarray
сохраняется конечное поле, поскольку <<<
оператор перенаправления добавляет LF к входной строке, и поэтому конечное поле не было пустым (в противном случае оно было бы удалено). Мы можем позаботиться об этом, явно сбросив окончательный элемент массива:
readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
Остались только две проблемы, которые на самом деле связаны между собой: (1) посторонние пробелы, которые необходимо обрезать, и (2) отсутствие поддержки разделителей из нескольких символов.
Пробельные символы, конечно, могут быть обрезаны позже (например, см. Как обрезать пустые места из переменной Bash? ). Но если мы сможем взломать разделитель из нескольких символов, то это решит обе проблемы за один раз.
К сожалению, нет прямого способа заставить работать разделитель из нескольких символов. Лучшее решение, о котором я подумал, - это предварительная обработка входной строки для замены разделителя из нескольких символов на символьный разделитель, который гарантированно не будет конфликтовать с содержимым входной строки. Единственный символ, имеющий эту гарантию, - это байт NUL . Это связано с тем, что в bash (хотя, впрочем, и не в zsh) переменные не могут содержать байт NUL. Этот шаг предварительной обработки может быть выполнен в процессе подстановки процесса. Вот как это сделать с помощью awk :
readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
Там наконец-то! Это решение не будет ошибочно разделять поля посередине, не будет преждевременно вырезаться, не будет сбрасывать пустые поля, не будет повреждаться при расширении имени файла, не будет автоматически убирать начальные и конечные пробелы, не будет оставлять промежуточный LF на конце, не требует циклов и не соглашается с разделителем из одного символа.
Решение для обрезки
Наконец, я хотел продемонстрировать свое собственное довольно сложное решение для обрезки, используя неясную -C callback
опцию readarray
. К сожалению, мне не хватает места для драконовского предела в 30 000 символов в Stack Overflow, поэтому я не смогу это объяснить. Я оставлю это как упражнение для читателя.
function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
,
(запятую), а не один символ, такой как запятая. Если вас интересует только последнее, ответы здесь проще найти: stackoverflow.com/questions/918886/…