Как удобно использовать одиночные кавычки или экранировать всю командную строку в Bash?
Введение
Рассмотрим такой инструмент, как
watch который может принимать другую команду (например,
ls -l) как это:
watch ls -l
# or equivalently
watch 'ls -l'
Если команда более сложная, все дело в цитировании / экранировании, если такие вещи, как
$variable,
*,
|,
; или же
&& интерпретируются текущей оболочкой или переходят к
watch. Эти две команды разные:
watch echo "$$"
watch 'echo "$$"'
Так вот:
watch date ; echo done
watch date \; echo done
sshаналогично, он может принимать один или несколько аргументов и создавать команду для запуска на сервере. Это зависит от цитирования / экранирования, если локальная оболочка интерпретирует
$variable,
| и тому подобное, или удаленная оболочка.
И есть команды вроде
sh -cкоторые требуют одного аргумента, содержащего код, но необходимость в кавычках / экранировании аналогична. по факту
watch (или же
ssh) строит этот единственный аргумент для
sh -c или аналогичная команда.
Проблема
У меня уже есть точная команда, которую я хочу передать
watch или же
sshили аналогичный инструмент. Команда извлекается из истории, вставляется в командную строку или набирается так, как будто она будет выполняться напрямую; это дословно в моей командной строке. Ничто в команде не должно расширяться или интерпретироваться до того, как
watchили аналогичный инструмент. В случае
watchэто означает, что все расширения и интерпретации должны происходить позже, периодически. В случае
ssh значит, это должно происходить на сервере.
Теперь мне нужно сделать две вещи:
- Добавить
watch(или что-то еще) в начале. Это не является проблемой. Я могу сделать это последним. Цитируйте и / или экранируйте исходную команду. Как правило, команда может включать одинарные кавычки, поэтому простое использование одинарных кавычек вслепую не является решением. Думаю, я знаю правильное общее решение:
- заменить каждый
'с участием'"'"'или с'\'', - заключить всю полученную строку в одинарные кавычки;
но делать это вручную утомительно и чревато ошибками.
- заменить каждый
Вопрос
Могу ли я заставить Bash правильно добавлять один уровень одиночных кавычек / экранирования ко всей командной строке по запросу?
(Примечание: вопрос возник, когда я отвечал на этот.)
1 ответ
Решение
Да. Моя идея - вызвать функцию оболочки (при нажатии клавиши), которая будет управлять
READLINE_LINE используя
${variable@Q} характерная черта.
Соответствующие части документации:
${parameter@operator}Расширение - это либо преобразование значения
parameterили информация оparameterсамо по себе, в зависимости от стоимостиoperator. Каждыйoperatorэто одна буква:
Q
Расширение - это строка, которая является значениемparameterцитируется в формате, который можно повторно использовать в качестве входных данных.
(источник)
READLINE_LINE
Содержимое строкового буфера Readline для использования сbind -x[…].
(источник)
В Bash 4.4.20 работает следующее:
_quote_all() { READLINE_LINE="${READLINE_LINE@Q}"; }
bind -x '"\C-x\C-o":_quote_all'
Для проверки решения подготовьте команду в командной строке (не выполнять), например
d="$(LC_ALL=C date)"; printf 'It'\''s now %s\n' "$d"
(Цитирование и всю команду можно упростить. Это сделано намеренно. И вы можете выполнить ее, чтобы убедиться, что это действительная команда, но поместите ее обратно в командную строку, прежде чем продолжить.)
Нажмите Ctrl+x,Ctrl+,o и он будет правильно процитирован / экранирован для наших целей. Это будет выглядеть так:
'd="$(LC_ALL=C date)"; printf '\''It'\''\'\'''\''s now %s\n'\'' "$d"'
Теперь все, что вам нужно сделать, это добавить
watch (или же
ssh …или что-то еще) впереди и выполнить. Если это
watch тогда обратите внимание, что заголовок похож на
Every 2.0s: d="$(LC_ALL=C date)"; printf 'It'\''s now %s\n' "$d"
Он содержит исходную команду. Команда правильно попала в
watch, никакая часть не была интерпретирована преждевременно.
Улучшения
Для удобства рассмотрим этот вариант:
_quote_all() { READLINE_LINE=" ${READLINE_LINE@Q}"; READLINE_POINT=0; }
Он подготовит строку и поместит курсор в начало, чтобы вы могли ввести
watchнемедленно. Или, может быть, даже этот вариант (он намеренно носит другое имя, мы создаем для него отдельную привязку):
_prepend_watch() { READLINE_LINE="watch ${READLINE_LINE@Q}"; READLINE_POINT=6; }
bind -x '"\C-x\C-w":_prepend_watch'
Теперь Ctrl+x,Ctrl+w обрабатывает цитирование, вставки
watch автоматически и помещает курсор в нужное место, чтобы вы могли ввести параметры.
С еще одной функцией, использующей
READLINE_POINT возможен следующий сценарий: введите
watch (или же
ssh …), за которой следует команда, где кавычки / экранирование - это как если бы команда была выполнена напрямую. Поместите курсор в то место, где начинается команда, нажмите клавишу и позвольте функции изменить все, от курсора до конца строки. Я не предоставляю здесь такую функцию; напишите это самостоятельно, если вам это нужно.
Йо Дог, я слышал, тебе нравятся цитаты
Можно сложить раствор. Я имею в виду, ты можешь уйти от этого
df -h | grep -v tmpfs
к этому
watch 'df -h | grep -v tmpfs'
к этому
ssh hostB -t 'watch '\''df -h | grep -v tmpfs'\'''
к этому
ssh -t hostA 'ssh -t hostB '\''watch '\''\'\'''\''df -h | grep -v tmpfs'\''\'\'''\'''\'''
(Да, я знаю
ssh -J)
просто нажимая Ctrl+x,Ctrl+o и добавляя одно или несколько слов впереди на каждом шаге.