Как удобно использовать одиночные кавычки или экранировать всю командную строку в 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 и добавляя одно или несколько слов впереди на каждом шаге.