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

Другие вопросы по тегам