Как разбить строку на массив в bash

У меня проблема с выходом программы. Мне нужно запустить команду в bash и вывести ее вывод (строку) и разбить ее, чтобы добавить новые строки в определенные места. Строка выглядит так:

battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 

в основном это значение xxx.yy.zz:, но значение может содержать пробелы. Вот результат, который я хотел бы получить

 battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 

У меня есть идея поиска первой точки, а затем оглянуться назад с этой позиции в пространстве, чтобы разместить там новую строку, но я не уверен, как ее достичь в Bash. Я все еще новичок.

  • Преобразование списка с разделителями строк с пробелами в аргументы
  • Как я могу использовать vi для редактирования строки подсказки утилиты?
  • Одинарная кавычка в двойных кавычках и справочное руководство Bash
  • 7z из сценария bash не исключает каталогов
  • Разделить страницы в pdf
  • понимание значения по умолчанию IFS
  • Вопрос возврата каретки
  • Командная строка (PS1), включая количество файлов в каталоге (как скрытых, так и обычных)
  • 6 Solutions collect form web for “Как разбить строку на массив в bash”

    Чистое решение bash, никаких внешних инструментов, используемых для обработки строк, просто расширение параметра:

     #! /bin/bash str='battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500' IFS=: read -a fields <<< "$str" for (( i=0 ; i < ${#fields[@]} ; i++ )) ; do f=${fields[i]} notfirst=$(( i>0 )) last=$(( i+1 == ${#fields[@]} )) (( notfirst )) && echo -n ${f% *} start=('' $'\n' ' ') colon=('' ': ') echo -n "${start[notfirst + last]}${f##* }${colon[!last]}" done echo 

    Объяснение: $notfirst и $last являются булевыми. Часть перед последним пространством ${f% *} не печатается для первого поля, так как такой вещи нет. $start и $colon содержат различные строки, которые разделяют поля: в первом элементе, не notfirst + last равен 0, поэтому ничего не добавляется, для остальных строк $notfirst равно 1, поэтому печатается $notfirst , а для последняя строка, добавление дает 2, поэтому печатается пробел. Затем часть после последнего пробела печатается ${f##* } . Колон печатается для всех строк, кроме последнего.

    Решение perl :

     $ perl -pe 's{\S+:}{$seen++ ? "\n$&" : "$&"}ge' file battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 

    объяснение

    • \S+: соответствует концу строки с :
    • Со всеми согласованными строками мы вставляем перед ними новую строку ("\n$&") кроме первой ($seen++) .

    С помощью GNU sed вы можете сопоставить каждую непрерывную строку (т. Е. Без пробелов), завершенную : а затем поместите новую строку перед всеми, кроме первой:

     sed 's/[^[:space:]]\+:/\n&/g2' 

    Если ваша версия sed не поддерживает расширение gn , вы можете использовать простой g модификатор

     sed 's/[^[:space:]]\{1,\}:/\ &/g' 

    который будет работать одинаково, за исключением печати новой строки новой строки перед первым ключом. Вы можете использовать perl -pe 's/\S+:/\n$&/g' с той же оговоркой (может быть perl-эквивалент GNU sed g2 но я этого не знаю).

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

     $ s="battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500" $ grep -oP '\S+:\s+.*?(?=\s+\S+:|$)' <<< "$s" battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 

    Если вы хотите получить результат в массиве:

     $ IFS=$'\n' foo=($(grep -oP '\S+:\s+.*?(?=\s+\S+:|$)' <<< "$s")) $ for i in "${!foo[@]}"; do echo "$i<==>${foo[i]}"; done 0<==>battery.charge: 90 1<==>battery.charge.low: 30 2<==>battery.runtime: 3690 3<==>battery.voltage: 230.0 4<==>device.mfr: MGE UPS SYSTEMS 5<==>device.model: Pulsar Evolution 500 

    EDIT: Объяснение регулярного выражения:

     '\S+:\s+.*?(?=\s+\S+:|$)' 
    • \S+ соответствует одному или нескольким символам без пробелов
    • : совпадения :
    • \s+ соответствует одному или нескольким пробелам после :
    • .*? обозначает неживое соответствие
    • (?=\s+\S+:|$) является исходным утверждением, чтобы определить, есть ли:
      • одно или несколько пробелов, за которыми следует строка (не-пробельные символы) и двоеточие, или
      • конец строки

    Таким образом, строка разделяется на части, такие как battery.charge: 90 , … device.mfr: MGE UPS SYSTEMS , …


    Ниже приведены ссылки на пару онлайн-анализаторов регулярных выражений:

    Вот наивный подход, который должен работать, если вы не заботитесь о том, чтобы вкладки и символы новой строки во входном (если есть) были преобразованы в простые пространства.

    Идея проста: разбивайте входные данные на пробелы и печатайте каждый токен, за исключением того, что вы добавляете токены, которые заканчиваются : с помощью новой строки (и снова добавьте пространство перед другими). Переменная $count и related if полезны только для предотвращения начальной пустой строки. Может быть удалено, если это не проблема. (Сценарий предполагает, что вход находится в файле с именем intput в текущем каталоге.)

     #! /bin/bash count=0 for i in $(<input) ; do fmt= if [[ $i =~ :$ ]] ; then if [[ $count -gt 0 ]] ; then fmt="\n%s" else fmt="%s" fi ((count++)) else fmt=" %s" fi printf "$fmt" "$i" done echo echo "Num items: $count" 

    Я надеюсь, что кто-то может придумать лучшую альтернативу.

     $ cat input battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 $ ./t.sh battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 Num items: 6 

    Вы можете использовать awk (1) со следующим скриптом split.awk:

     BEGIN { RS=" "; first=1; } first { first=0; printf "%s", $1; next; } /[az]+\.[^:]+:/ { printf "\n%s", $1; next; } { printf " %s", $1 } END { printf "\n" } 

    Когда вы запускаете

     awk -f split.awk input.dat 

    ты получишь

     battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500 

    Идея состоит в том, чтобы позволить awk разбить вход, когда видит пространство (установка разделителя записей RS в строке 1). Затем он соответствует xxx.yy.zz: значениям в строках 2 и 3 (отличая первое совпадение от последующих), а строка 4 – всякий раз, когда строки 2 и 3 не совпадают. Строка 5 просто напечатает последнюю строку новой строки.

    Linux и Unix - лучшая ОС в мире.