Проблема возникает из-за того, что подстановки команд и переменных выполняются в здесь-документах до их передачи команде. Таким образом, $(sudo cat /etc/origin/master/ca-bundle.crt; echo x)
и ${RESULT%x}
оцениваются на локальном компьютере (где /etc/origin/master/ca-bundle.crt предположительно не существует, а RESULT
не определен). В результате вот что отправляется на удаленный компьютер:
RESULT="x"
echo ""
Это совсем не то, что вы хотели.
Чтобы избежать этого, вы можете либо экранировать $
символов в здесь-документе, либо заключить в кавычки разделитель здесь-документа (<<'EOF'
). Кстати, символы отступа в здесь-документе будут передаваться через соединение ssh
, что может иметь неприятные последствия (например, вызов функций автозаполнения удаленной оболочки). Я бы либо выделил здесь документ, либо поставил перед разделителем префикс «-» (<<-'EOF'
) и использовал табуляцию (а не пробелы) для отступа.
РЕДАКТИРОВАТЬ: Подумав об ответе @tripleee, я не уверен, что согласен со всеми его предложенными переписываниями, но он, безусловно, прав в том, что части цикла более сложны и/или хрупки, чем они должны быть. Вот мои предложения по изменениям:
Если на удаленном компьютере не делается больше, чем показано в вопросе, часть ssh
может (и должна) быть радикально упрощена. Нет необходимости помещать содержимое файла сертификата в переменную, а затем отображать переменную, просто выведите ее напрямую. И обратите внимание, что вся проблема возникла из-за сложностей с вводом и выводом переменной; если бы удаленная часть была просто sudo cat /etc/origin/master/ca-bundle.crt
, все бы просто работало. Кроме того, нет необходимости явно запускать удаленную оболочку bash и использовать перенаправление, чтобы дать ей команду для запуска, просто используйте ssh ${host} sudo cat /etc/origin/master/ca-bundle.crt
.
Хорошо, есть одна вещь, которую сделали переменные. Он добавил пустую строку после содержимого файла сертификата. Это происходит из-за того, что трюк с "x" тщательно сохраняет последнюю новую строку в содержимом файла (обычно $( )
удаляет конечные символы новой строки), а затем echo
добавляет еще одну новую строку, в результате чего получается пустая строка. Но если это было сделано намеренно, то гораздо проще (и понятнее) просто добавить еще одну команду echo
без аргументов.
Конвейер для перечисления имен хостов намного длиннее, чем нужно, но здесь есть компромисс между сложностью и удобочитаемостью/ясностью. Версия tripee намного короче, но из нее сложнее понять, что она делает (если вы не владеете свободно awk
), а это означает, что существует больше риска ошибок, ее сложнее поддерживать и т. д. Но есть еще некоторые упрощения, которые я бы хотел рекомендовать:
Не используйте cat
для передачи одного файла в конвейер. Либо используйте перенаправление <filename
, либо (если команда его поддерживает) просто дайте команде имя файла и дайте ей прочитать из него:
cut -d ' ' -f 1 /etc/ansible/hosts | ...
cut -d ' ' -f 1 </etc/ansible/hosts | ...
Это может показаться менее естественным, чем использование cat |
, но это лучший (и самый стандартный) способ сделать что-то. Откровенно говоря, если это кажется неестественным или запутанным, это просто означает, что вы сделали это недостаточно. Так что делайте это больше.
В вашем пайплайне каждая команда делает только одно: cut
получает первое поле, grep
выбирает те, которые начинаются с "master-", sort
упорядочивает их, а uniq
удаляет дубликаты. Но sort
вполне способен сам удалять дубликаты, поэтому я бы использовал sort -u
вместо sort | uniq
. И awk
может делать почти все сам по себе (но становится трудно читать/понимать, если вы заставите его делать слишком много). Лично я бы использовал:
awk '$1 ~ /^master-/ {print $1}' /etc/ansible/hosts | sort -u
(Этот сценарий awk
можно прочитать как «если первое поле начинается с 'master-', напечатайте его.)
Тогда возникает вопрос, как перебрать список хостов. for var in $(somecommand)
широко считается антипаттерном из-за множества вещей, которые могут пойти не так. Он разбивает вывод команды на «слова» (которые могут соответствовать или не соответствовать строкам), а затем пытается преобразовать все найденные подстановочные знаки в списки соответствующих файлов (что может иметь действительно странные эффекты ), а затем перебирает результат этого.
В данном конкретном случае, я думаю, вы в безопасности (если только вы не переопределили $IFS
). В ваших записях не должно быть разделителей слов (пробелов, табуляции и новой строки), потому что вы уже выбираете только первое поле. В записях не должно быть подстановочных знаков, потому что «*», «?» и «[» обычно не допускаются в именах хостов. Я предполагаю, что может быть необработанный IPv6-адрес в формате "[hexdigits:hexdigits::hexdigits]", который является подстановочным знаком оболочки и будет вызывать проблемы, если есть совпадения файлы, но они не будут выбраны по шаблону ^master-
.
В качестве альтернативы можно использовать передачу списка либо команде xargs
(как в ответе @tripleee), либо циклу while read
. У них обоих есть другая потенциальная проблема, заключающаяся в том, что ssh
может украсть имена хостов из входного потока, если вы не будете осторожны. ssh -n
предотвратит это.
В этом случае я думаю, что просто использовал бы цикл for
. Итак, со всем этим, вот мое предложенное переписывание:
#!/bin/bash
set -u
for host in $(awk '$1 !~ /^master-/ {print $1}' /etc/ansible/hosts | sort -u); do
ssh "${host}" sudo cat /etc/origin/master/ca-bundle.crt
echo
done
person
Gordon Davisson
schedule
17.11.2017
sudo
ed, который не наследовал FD от родителя; это связано с подстановкой команд в здесь-документах. Что еще более важно, решения в другом вопросе здесь неприменимы. - person Gordon Davisson   schedule 17.11.2017