Различные способы разбиения строк без учета пробелов и замены на новые строки в скрипте bash
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Total Bash Noob, так что извините за все, что явно глупо в этом вопросе. Мне действительно начинает нравиться это, однако, это напоминает мне о моих первых месяцах с C, когда мне было 7 или 8 лет:)
Введение: это часть скрипта, которая будет использоваться для выполнения определенных задач автоматического восстановления / восстановления на машинах, указанных по имени (т.е. --device mba
) если они требуют чистой установки. Это не только сэкономит много времени - несмотря на то, что на это у меня ушло время писать - и послужит способом изучения языка, его возможностей, возможностей и ограничений.
Я установил свой devices
массив пар ключ-значение следующим образом (дополнительный вопрос: есть devices=()
необходимо?, Я перестал быть ленивым и попробовал себя и ни к чему не нужно.)
# Devices
devices=()
devices[0]="mba=Lee's Macbook Air"
devices[1]="mms=Lee's Mac Mini Server"
# devices[2]="mml=Lee's Mac Mini (Living Room)"
# devices[3]="mmb=Lee's Mac Mini (Bedroom)"
# devices[4]="mps=Lee's Mac Pro Server"
Моя первая попытка, которая имеет смысл, состоит в том, чтобы просто перебрать массив и вывести device
Параметр, но подходит только для этого метода. В других частях скрипта мне нужно разбить строку, чтобы получить короткое имя.
for device in ${devices[@]}; do
echo $device
done
Не имеет смысла! Почему это расщепляется на пространствах?
Вторая попытка (мне нравится эта, так как позже мне понадобится получить ключ, чтобы я мог выполнять дополнительные действия на его основе, часть метода здесь простая вызывается, когда при вызове сценарий, а затем существует):
for device in ${devices[@]//=/}; do
echo ${device[0]} ${device[1]}
done
Или же:
for device in ${devices[@]}; do
deviceKeyValuePair=(${device//=/})
echo ${deviceKeyValuePair[0]} ${deviceKeyValuePair[1]}
done
Производит следующее:
MacBook Air от mbaLee Mac Mini Сервер mmsLee
3-я попытка:
for device in ${devices[@]}; do
deviceKeyValuePair=(`echo $device | tr "[:alnum:]" "[:alnum:]"`)
echo ${deviceKeyValuePair[0]} ${deviceKeyValuePair[1]}
done
Производит:
mba = MacBook Air Ли mms= Mac Mac сервер Ли
Я также попробовал метод TR:
deviceKeyValuePair=(`echo $device | tr "[:alnum:]" "[:alnum:]"`)
Выход:
mba = MacBook Air Ли mms= Mac Mac сервер Ли
Я мог бы использовать ассоциативные массивы, но они доступны только в BASH 4, и даже думал, что все машины были обновлены до El Capitan на этой неделе. BASH по-прежнему версии 3.2.57(1), так что это не пойдет, иначе следующее, скорее всего, сработало бы, Хотя я не являюсь поклонником этого метода... просто анальная неприязнь к итерации и массиву, а затем к поиску по ключу - иногда я просто очень странная вещь! Я делаю это со словарями и списками.NET все время. Следующее будет наиболее вероятно работать:
declare -A devices
devices["mba"]="Lee's Macbook Air"
devices["mms"]="Lee's Mac Mini Server"
devices["mml"]="Lee's Mac Mini (Living Room)"
devices["mmb"]="Lee's Mac Mini (Bedroom)"
devices["mps"]="Lee's Mac Pro Server"
for device in ${!devices[@]}; do
echo ${device} ${devices[${device}]}
done
Безопасно ли обновлять bash на OS X El Capitan? Я что-нибудь сломаю? На самом деле я все еще хотел бы знать...
Разобрался: IFS!!!!
function listDevices() {
oldIFS=IFS
IFS=''
for device in ${devices[@]}; do
deviceKeyValuePair=(${device//=/})
printf "${deviceKeyValuePair[0]}=${deviceKeyValuePair[1]}\n"
done
IFS=$oldIFS
exit 0
}
Вышеуказанное было бы моим предпочтительным способом сделать это, но производит:
MacBook Air от mbaLee = Mac Mini сервера mmsLee = Mac Mini (гостиная) mmlLee = Mac Mini (спальня) mmbLee = Mac Pro Server mpsLee =
Я думаю, мое понимание //=/
в (${device//=/})
это неверно? Я подумал, что это собственный встроенный способ bash для разбиения строки на основе указанного разделителя. Больше похоже на способ удаления символа с помощью bash, поэтому еще один вопрос, поскольку я не могу найти ссылку на него на странице манипуляции с bash-строкой!
Во всяком случае, я остановился на следующем:
function listDevices() {
# Pointless since this method exits once complete
# oldIFS=IFS
IFS=''
for device in ${devices[@]}; do
deviceKey=${device%%=*}
deviceName=${device##*=}
echo "$deviceKey: ${deviceName}"
done
# Pointless since this method exits once complete
# IFS=$oldIFS
exit 0
}
Какие выводы:
mba: Ли MacBook Air ммс: Lee Mac Mini Server ммл: Lee Mac Mini (Гостиная) mmb: Ли Mac Mini (спальня) mps: Lee Mac Pro Server
Я целую вечность писал этот пост, и все еще есть вопросы, на которые нужно ответить (подразумеваемые и явные), так что это две причины, по которым он не должен быть пересмотрен, плюс он послужит для обучения других новичков, которые не читают руководства полностью, у меня есть причина в форме тяжелого СДВГ без гиперактивности, поэтому ДОБАВЛЯЮТ, но не все знают, что это значит, и я никогда не читал руководств для устройств, и при этом я не смотрел на брошюру с инструкциями Lego®, за исключением Lego@ Technics. Кроме того, кто-то может показать мне гораздо более хороший способ, не связанный с IFS...
Спасибо, парни!!!
Комментарий: у меня долгий путь, чтобы овладеть сценариями bash и shell! Кроме того, я изучаю варианты Groovy и SmartThings и Lua одновременно. Это ДОБАВИТЬ для вас.
РЕДАКТИРОВАТЬ: Надеюсь, это отвечает интересам Кира:
#!/bin/bash
# Devices
devices[0]="mba=Lee's Macbook Air"
devices[1]="mms=Lee's Mac Mini Server"
devices[2]="mml=Lee's Mac Mini (Living Room)"
devices[3]="mmb=Lee's Mac Mini (Bedroom)"
devices[4]="mps=Lee's Mac Pro Server"
function listDevices() {
# Pointless since this method exits once complete
# oldIFS=IFS
IFS=''
for device in ${devices[@]}; do
deviceKey=${device%%=*}
deviceName=${device##*=}
echo "$deviceKey: ${deviceName}"
done
# Pointless since this method exits once complete
# IFS=$oldIFS
exit 0
}
listDevices
На самом деле это не так. В будущем я приложу больше усилий, постараюсь быть менее многословным и обеспечить, чтобы другие пользователи могли копировать и вставлять код в файл и выполнять его.
2 ответа
Одна из стандартных ошибок в сценариях оболочки заключается в том, что (почти) все, что не в кавычках, будет разбито на слова (на основе символов в IFS
), а также подстановочный знак, расширенный до списка совпадающих имен файлов. Если вы специально не хотите, чтобы это произошло (а вы обычно этого не делаете), вы должны помещать такие вещи, как ссылки на переменные, в двойные кавычки. (Настройка IFS
чтобы "" эффективно отключил расщепление, но оставляет расширение по шаблону, так что это не так хорошо. У этого также есть другие, потенциально неприятные, побочные эффекты.) Так что оставьте ISF
в одиночку и напишите вашу команду цикла следующим образом:
for device in "${devices[@]}"; do
Во-вторых, замена как ${device//=/}
заменит каждое "=" на... ничего. По сути, он удалит знаки равенства из строки. Если вы использовали ${device//=/ }
это превратит их в пробелы, но это не то, что вы хотите, потому что тогда они неотличимы от пробелов в правой части. Подход, на котором вы остановились, почти верен, но deviceKey=${device%%=*}
уравновешивания, начинающиеся с самого левого "=", и deviceName=${device##*=}
обрезает крайнее правое "=". Если в строке всегда есть ровно один "=", это нормально, но если имя устройства может содержать "=", вам следует использовать deviceName=${device#*=}
(обратите внимание только на одно "#"), поэтому оно обрезает наименьшее совпадение, т.е. через крайнее левое "=". Кстати, это один из немногих случаев, когда безопасно оставлять ссылки на переменные без кавычек, но в любом случае проще и безопаснее просто заключить двойные кавычки, чем пытаться точно помнить, когда это безопасно, а когда - нет. Таким образом:
deviceKey="${device%%=*}"
deviceName="${device#*=}"
И на самом деле, есть способ на основе массива для разделения:
IFS="=" read -a deviceKeyValuePair <<<"$devices"
... здесь двойные кавычки вокруг "$devices"
предотвратить расщепление слов и расширение подстановочных знаков перед передачей строки read
, read
разбивает строку в массив на основе IFS
, Обратите внимание, что IFS
настройка используется в качестве префикса к read
команда, так что она применяется только к этой одной команде (то есть вам не нужно сбрасывать ее позже). Но, как и в вашей версии, у этого есть проблема, если имя устройства содержит "=", потому что оно разделит его на дополнительные элементы массива на основе этого... так что на самом деле я бы рекомендовал не использовать это.
IFS была причиной проблемы. Или, точнее, мое непонимание его цели.
$IFS (внутренний разделитель полей)
Эта переменная определяет, как Bash распознает поля или границы слов, > когда он интерпретирует символьные строки.
По умолчанию для $IFS используются пробелы (пробел, табуляция и новая строка), но они могут быть изменены.
Источник: Advanced Bash-Scripting Guide
Итак, постановка IFS=''
решил проблему. Также при условии, что эта функция не завершает работу после завершения, значение IFS
должны быть сохранены перед его изменением и восстановлены после завершения, например:
function listDevices() {
previousIFS=IFS
IFS=''
for device in ${devices[@]}; do
deviceKey=${device%%=*}
deviceName=${device##*=}
echo "$deviceKey: ${deviceName}"
done
IFS=$previousIFS
}