Цитування в ssh $ host $ FOO та ssh $ host "sudo su user -c $ FOO"


30

Я часто закінчую видачу складних команд через ssh; ці команди включають в себе трубопровід для пробудження або перл одно рядків, і в результаті містять одинарні лапки та $. Я ні в якому разі не зміг визначити важке і швидке правило, як правильно робити цитування, і не знайшов для цього хорошого посилання. Наприклад, врахуйте наступне:

# what I'd run locally:
CMD='pgrep -fl java | grep -i datanode | awk '{print $1}'
# this works with ssh $host "$CMD":
CMD='pgrep -fl java | grep -i datanode | awk '"'"'{print $1}'"'"

(Зверніть увагу на додаткові цитати у заяві awk.)

Але як я можу отримати це працювати, наприклад ssh $host "sudo su user -c '$CMD'"? Чи існує загальний рецепт управління цитатами в таких сценаріях? ..

Відповіді:


35

Робота з декількома рівнями цитування (насправді, декількома рівнями розбору / інтерпретації) може ускладнитися. Це допомагає пам’ятати про кілька речей:

  • Кожен "рівень котирування" потенційно може містити різну мову.
  • Правила цитування залежать від мови.
  • У роботі з більш ніж одним або двома вкладеними рівнями, як правило, найпростіше працювати "знизу, вгору" (тобто від найглибшого до зовнішнього).

Рівні цитування

Давайте розглянемо ваші приклади команд.

pgrep -fl java | grep -i datanode | awk '{print $1}'

Ваша перша команда прикладу (вище) використовує чотири мови: оболонку, регулярний вираз у pgrep , регулярний вираз у grep (що може відрізнятися від мови регулярного виразів у pgrep ) та awk . Задіяні два рівні інтерпретації: оболонка та один рівень після оболонки для кожної із залучених команд. Є лише один явний рівень котирування (цитування оболонки в awk ).

ssh host 

Далі ви додали рівень ssh зверху. Це фактично інший рівень оболонки: ssh не інтерпретує саму команду, передає її оболонці на віддалений кінець (через (наприклад) sh -c …), і ця оболонка інтерпретує рядок.

ssh host "sudo su user -c …"

Потім ви запитали про додавання іншого рівня оболонки в середині за допомогою su (через sudo , який не інтерпретує його аргументи команд, тому ми можемо ігнорувати його). На даний момент у вас три рівні гніздування ( awk → shell, оболонка → shell ( ssh ), оболонка → оболонка ( su user -c ), тому я раджу використовувати підхід «знизу, вгору». ваші снаряди сумісні з Борном (наприклад, ш , зола , тире , кш , баш , зш тощо). Деякі інші види оболонки ( риба , рстощо) може вимагати різного синтаксису, але метод все-таки застосовується.

Внизу, вгору

  1. Сформулюйте рядок, який ви хочете представляти на найглибшому рівні.
  2. Виберіть механізм цитування з репертуару цитування наступної найвищої мови.
  3. Цитуйте потрібний рядок відповідно до обраного механізму цитування.
    • Часто існує багато варіацій, як застосувати який механізм котирування. Робити це вручну, як правило, є питанням практики та досвіду. Роблячи це програмно, зазвичай, найкраще вибрати найпростіший, щоб правильно вийти (як правило, "найбільш буквальне" (найменше втечі)).
  4. За бажанням використовуйте отриманий рядок, що цитується, з додатковим кодом.
  5. Якщо ви ще не досягли бажаного рівня цитування / інтерпретації, візьміть отриманий рядок (цитуючи доданий код) та використовуйте його як початковий рядок на кроці 2.

Цитуючи семантику Вари

Тут слід пам’ятати, що кожна мова (рівень котирування) може дати трохи різну семантику (або навіть різко різну семантику) одному і тому ж символу цитування.

Більшість мов мають "буквальний" механізм цитування, але вони різняться в тому, наскільки вони буквальні. Одинарна цитата оболонок Борна насправді буквальна (а це означає, що ви не можете використовувати її для цитування самого одного символу цитати). Інші мови (Perl, Ruby) менш буквальні в тому , що вони інтерпретують деякі зворотні косі риси послідовностей всередині окремі цитовані регіонів , НЕ в буквальному сенсі ( в приватність, \\і \'результат \і ', але і інші послідовності з зворотними косою рисою, на самому ділі дослівні).

Вам потрібно буде прочитати документацію для кожної з мов, щоб зрозуміти її правила котирування та загальний синтаксис.

Ваш приклад

Найпотаємніший рівень вашого прикладу - це програма awk .

{print $1}

Ви збираєтеся вставити це в командний рядок оболонки:

pgrep -fl java | grep -i datanode | awk 

Ми повинні захистити (як мінімум) простору і $в AWK програмі. Очевидним вибором є використання однієї цитати в оболонці навколо всієї програми.

  • '{print $1}'

Однак є й інші варіанти:

  • {print\ \$1} безпосередньо вирватися з космосу і $
  • {print' $'1} єдина цитата лише простір і $
  • "{print \$1}" подвійну цитату цілого і втекти від $
  • {print" $"1}подвійний цитата лише пробіл, і $
    це може бути трохи згинати правила (немальований $в кінці рядка з подвійним цитуванням є буквальним), але, здається, він працює в більшості оболонок.

Якщо програма використовувала кому між відкритими і закритими фігурними дужками, нам також потрібно було б процитувати або вийти з коми або фігурних дужок, щоб уникнути "розширення дужки" в деяких оболонках.

Ми вибираємо '{print $1}'і вставляємо його в решту оболонки «код»:

pgrep -fl java | grep -i datanode | awk '{print $1}'

Далі ви хотіли запустити це через su і sudo .

sudo su user -c 

su user -c …так само some-shell -c …(за винятком запуску під деяким іншим UID), тому su просто додає ще один рівень оболонки. sudo не інтерпретує свої аргументи, тому не додає рівнів цитування.

Нам потрібен інший рівень оболонки для нашого командного рядка. Ми можемо знову вибрати одне котирування, але ми мусимо надати особливу обробку існуючим єдиним котируванням. Звичайний спосіб виглядає так:

'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'

Тут є чотири рядки, які оболонка буде інтерпретувати та об'єднувати: перший сингл ( pgrep … awk), що цитується, (єдиний цитат , що уникнув, програма awk з одною цитатою, інша цитата, що уникнула).

Звичайно, існує безліч альтернатив:

  • pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1} уникнути всього важливого
  • pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}те саме, але без зайвих пробілів (навіть у програмі awk !)
  • "pgrep -fl java | grep -i datanode | awk '{print \$1}'" подвійно цитуйте всю річ, уникайте $
  • 'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"Ваша варіація; трохи довше, ніж звичайний спосіб, завдяки використанню подвійних лапок (два символи) замість втечі (один символ)

Використання різних котирувань на першому рівні дозволяє отримати інші варіанти на цьому рівні:

  • 'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
  • 'pgrep -fl java | grep -i datanode | awk {print\ \$1}'

Вбудовуючи першу варіацію в командний рядок sudo / * su *, надайте це:

sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'

Ви можете використовувати ту саму рядок у будь-якому іншому контексті одного оболонки (наприклад ssh host …).

Далі ви додали рівень ssh зверху. Це фактично інший рівень оболонки: ssh не інтерпретує команду, а передає її оболонці на віддалений кінець (через (наприклад) sh -c …), і ця оболонка інтерпретує рядок.

ssh host 

Процес той самий: візьміть рядок, виберіть метод цитування, використовуйте його, вставте його.

Використання одинарних лапок:

'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

Зараз є одинадцять рядків, які інтерпретуються та об'єднуються:, 'sudo su user -c 'уникнув єдиний цитат, 'pgrep … awk 'уникнув єдину цитату, уникнув зворотній косий ривок, дві уникнуті одиночні цитати, єдину програму awk , єдину цитату, що уникнув, та остаточну цитату, що уникнув .

Заключна форма виглядає приблизно так:

ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

Це трохи важко вводити вручну, але буквальний характер єдиного котирування оболонки дозволяє легко автоматизувати невеликі зміни:

#!/bin/sh

sq() { # single quote for Bourne shell evaluation
    # Change ' to '\'' and wrap in single quotes.
    # If original starts/ends with a single quote, creates useless
    # (but harmless) '' at beginning/end of result.
    printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}

# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }

ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"

ssh host "$(sq "$s2")"

5
Чудове пояснення!
Жил 'ТАК - перестань бути злим'

7

Дивіться відповідь Кріса Джонсена для чіткого, глибокого пояснення із загальним рішенням. Я дам кілька додаткових порад, які допомагають у деяких звичайних обставинах.

Одиночні цитати уникають усього, окрім однієї цитати. Тож якщо ви знаєте, що значення змінної не включає жодної цитати, ви можете безпечно її інтерполювати між окремими лапками в сценарії оболонки.

su -c "grep '$pattern' /root/file"  # assuming there is no ' in $pattern

Якщо ваша локальна оболонка ksh93 або zsh, ви можете впоратися з одинарними лапками в змінній, переписавши їх у '\''. (Хоча в bash також є ${foo//pattern/replacement}конструкція, його обробка єдиних лапок не має для мене сенсу.)

su -c "grep '${pattern//'/'\''}' /root/file"  # if the outer shell is zsh
su -c "grep '${pattern//\'/\'\\\'\'}' /root/file"  # if the outer shell is ksh93

Ще одна порада, щоб уникнути необхідності мати справу з вкладеним цитуванням, - максимально пропускати рядки через змінні середовища. Ssh і sudo, як правило, скидають більшість змінних середовища, але вони часто налаштовані пропускати LC_*, оскільки вони, як правило, дуже важливі для зручності використання (вони містять інформацію про місцевості) і рідко вважаються чутливими до безпеки.

LC_CMD='what you would use locally' ssh $host 'sudo su user -c "$LC_CMD"'

Тут, оскільки LC_CMDмістить фрагмент оболонки, він повинен бути наданий буквально до самої внутрішньої оболонки. Тому змінна розширюється оболонкою безпосередньо вгорі. Внутрішня оболонка "$LC_CMD", але одна, бачить команди , і найглибша оболонка бачить команди.

Подібний метод корисний для передачі даних у утиліту для обробки тексту. Якщо ви використовуєте інтерполяцію оболонки, утиліта буде розглядати значення змінної як команду, наприклад sed "s/$pattern/$replacement/", не буде працювати, якщо змінні містять /. Тому використовуйте awk (не sed), або його -vопцію, або ENVIRONмасив, щоб передавати дані з оболонки (якщо ви проходите ENVIRON, не забудьте експортувати змінні).

awk -vpattern="$pattern" replacement="$replacement" '{gsub(pattern,replacement); print}'

2

Як Кріс Джонсон дуже добре описує , у вас тут є кілька рівнів цитованої непрямості; Ви інструктує ваш місцевий shellдоручити пульт дистанційного керування shellчерез sshякі він повинен доручити sudoдоручити suдоручити віддаленої shellзапустити конвеєр pgrep -fl java | grep -i datanode | awk '{print $1}'як user. Такий наказ вимагає багато нудних \'"quote quoting"\'.

Якщо ви приймете мою пораду, ви відмовитесь від усіх дурниць і зробите:

% ssh $host <<REM=LOC_EXPANSION <<'REMOTE_CMD' |
> localhost_data='$(commands run on localhost at runtime)' #quotes don't affect expansion
> more_localhost_data="$(values set at heredoc expansion)" #remote shell will receive m_h_d="result"
> REM=LOC_EXPANSION
> commands typed exactly as if located at 
> the remote terminal including variable 
> "${{more_,}localhost_data}" operations
> 'quotes and' \all possibly even 
> a\wk <<'REMOTELY_INVOKED_HEREDOC' |
> {as is often useful with $awk
> so long as the terminator for}
> REMOTELY_INVOKED_HEREDOC
> differs from that of REM=LOC_EXPANSION and
> REMOTE_CMD
> and here you can | pipeline operate on |\
> any output | received from | ssh as |\
> run above | in your local | terminal |\
> however | tee > ./you_wish.result
<desired output>

ДЛЯ БІЛЬШ:

Перевірте мою (можливо, занадто затяжну) відповідь на Piping paths з різними типами котирувань на заміну косої риси, в якій я обговорюю частину теорії, чому це працює.

-Майк


Це виглядає цікаво, але я не можу змусити його працювати. Чи можете ви розмістити мінімальний робочий приклад?
Джон Лоуренс Аспден

Я вважаю, що цей приклад вимагає zsh, оскільки він використовує кілька перенаправлень на stdin. В інших оболонках, подібних до Борна, другий <<просто замінює перший. Чи повинен десь сказати "лише зш", чи я щось пропустив? (Хоч хитрий трюк, щоб мати гередок, який частково піддається локальному розширенню)

Ось сумісна версія для bash: unix.stackexchange.com/questions/422489/…
dabest1

0

Як щодо використання більше подвійних лапок?

Тоді вам ssh $host $CMDслід добре працювати з цим:

CMD="pgrep -fl java | grep -i datanode | awk '{print $1}'"

Тепер до більш складного - ssh $host "sudo su user -c \"$CMD\"". Я думаю , все , що вам потрібно зробити , це уникнути чутливих персонажів CMD: $, \і ". Так що я хотів би спробувати і подивитися , якщо це працює: echo $CMD | sed -e 's/[$\\"]/\\\1/g'.

Якщо це виглядає гаразд, увімкніть echo + sed у функцію оболонки, і ви добре піти ssh $host "sudo su user -c \"$(escape_my_var $CMD)\"".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.