Ssh $ host $ FOO ve ssh $ host “sudo su user -c $ FOO” türünde alıntı


30

Genellikle ssh üzerinden karmaşık komutlar veririm; bu komutlar, tek satırları awk veya perl ile eşleştirmeyi içerir ve sonuç olarak tek tırnak işaretleri ve $ 'lar içerir. Ne düzgün alıntı yapmak zor ve hızlı bir kural bulamadım ne de iyi bir referans bulamadım. Örneğin, aşağıdakileri göz önünde bulundurun:

# 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 ifadesindeki fazladan alıntıları not edin.)

Fakat bununla çalışmasını nasıl sağlayabilirim, örneğin ssh $host "sudo su user -c '$CMD'"? Bu tür senaryolarda teklifleri yönetmek için genel bir tarif var mı? ..

Yanıtlar:


35

Birden fazla teklif seviyesiyle (gerçekten, birden fazla ayrıştırma / yorumlama düzeyi) ilgilenmek karmaşıklaşabilir. Bazı şeyleri akılda tutmaya yardımcı olur:

  • Her “alıntı seviyesi” potansiyel olarak farklı bir dil içerebilir.
  • Alıntı yapma kuralları dile göre değişir.
  • Birden fazla veya iki iç içe seviyeyle uğraşırken, genellikle "aşağıdan yukarıya" (en içten dışa doğru) çalışmak en kolaydır.

Teklif Seviyesi

Örnek komutlarınıza bakalım.

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

Kabuğun içinde regex: (yukarıda) İlk örnek komut Dört dil kullanır pgrep , içinde regex grep (regex dilinden farklı olabilir pgrep ) ve awk . Dahil olan iki yorum düzeyi vardır: kabuk ve ilgili komutların her biri için kabuktan sonra bir seviye. Yalnızca bir açık tırnak düzeyi vardır ( awk içine kabuk alıntı ).

ssh host 

Sonra üstüne ssh seviyesi eklendi . Bu etkili bir başka kabuk seviyesidir: ssh komutun kendisini yorumlamaz, uzak uçtaki bir kabuğa (örneğin (örneğin) sh -c …) verir ve bu kabuk dizeyi yorumlar.

ssh host "sudo su user -c …"

Daha sonra su kullanarak ortasına başka bir kabuk seviyesi eklemeyi istediniz ( komut bağımsız değişkenlerini yorumlamayan sudo aracılığıyla , böylece onu görmezden gelebiliriz). Bu noktada, üç iç içe geçme seviyesine sahipsiniz ( awk → kabuk, kabuk → kabuk ( ssh ), kabuk → kabuk ( su kullanıcısı -c ), bu yüzden "aşağı, yukarı" yaklaşımını kullanmanızı öneriyorum. mermileriniz Bourne uyumludur (ör. sh , kül , kısa çizgi , ksh , bash , zsh , vb.) Başka tür bir kabuk ( balık , rc, vb.) farklı sözdizimi gerektirebilir, ancak yöntem hala geçerlidir.

Altüst

  1. En iç düzeyde temsil etmek istediğiniz dizgiyi formüle edin.
  2. Bir sonraki en yüksek dilin alıntı repertuarından bir alıntı mekanizması seçin.
  3. İstediğiniz dizgiyi seçtiğiniz alıntı mekanizmasına göre alıntı yapın.
    • Hangi alıntı mekanizmasının nasıl uygulanacağı konusunda birçok değişiklik vardır. El ile yapmak genellikle bir uygulama ve tecrübe meselesidir. Programlı olarak yaparken, en doğru olanı seçmek en iyisidir (genellikle “en gerçek” (en az kaçış)).
  4. İsteğe bağlı olarak, elde edilen alıntı dizesini ek kodla kullanın.
  5. İstediğiniz alıntı / yorumlama seviyesine henüz ulaşmadıysanız, sonuçlanan alıntı dizgiyi (artı herhangi bir kod) alın ve 2. adımda başlangıç ​​dizgisi olarak kullanın.

Anlambilimsel Değişken Alıntılama

Burada akılda tutulması gereken şey, her dilin (alıntılama seviyesinin) aynı alıntılama karakterine biraz farklı anlambilim (veya çok daha farklı anlambilim) verebilmesidir.

Çoğu dilde “edebi” bir alıntı mekanizması vardır, ancak tam anlamıyla ne kadar hazır olduklarına göre değişir. Bourne benzeri mermilerin tekli alıntısı gerçektir (yani tek bir alıntı karakterinin kendisini alıntılamak için kullanamazsınız). Diğer diller (Perl, Ruby) daha az onlar yorumlamak o değişmez olduğu bazı sivil anlamıyla tek tırnaklı bölgelerin içinde ters eğik çizgi dizileri (özellikle, \\ve \'sonuç \ve 'ancak diğer ters eğik çizgi dizileri aslında edebi vardır).

Alıntılama kurallarını ve genel sözdizimini anlamak için dillerinizin her birinin belgelerini okumak zorunda kalacaksınız.

Senin örnek

Örneğinizin en içsel seviyesi bir awk programıdır.

{print $1}

Bunu bir kabuk komut satırına yerleştireceksiniz:

pgrep -fl java | grep -i datanode | awk 

Biz (en azından) alan ve korumak için gereken $de awk programı. Belirgin seçim, tüm programın etrafında tek bir alıntı kullanmaktır.

  • '{print $1}'

Yine de başka seçenekler var:

  • {print\ \$1} doğrudan boşluktan kaçmak ve $
  • {print' $'1} tek alıntı sadece boşluk ve $
  • "{print \$1}" bütünü iki katına çıkarmak ve kaçmak $
  • {print" $"1}sadece boşluğu iki katına çıkarır ve $
    bu kuralları biraz bükebilir ( $çift ​​tırnaklı bir dizgenin sonunda çıkılmaz), ancak çoğu kabukta işe yaradığı görülmektedir.

Program açık ve kapalı küme parantezleri arasında virgül kullanıyorsa, bazı mermilerdeki "küme genişlemesini" önlemek için virgül veya küme parantezlerinden alıntı yapmamız veya kaçmamız gerekir.

Biz almak '{print $1}'ve kabuk “kod” geri kalanında gömün:

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

Sonra, bunu su ve sudo ile çalıştırmak istediniz .

sudo su user -c 

su user -c …aynen some-shell -c …(başka bir UID altında çalışanlar hariç) olduğu gibi , su da başka bir kabuk seviyesi ekler. sudo , argümanlarını yorumlamaz, bu yüzden herhangi bir alıntı seviyesi eklemez.

Komut dizemiz için başka bir kabuk seviyesine ihtiyacımız var. Tek alıntıyı tekrar seçebiliriz, ancak mevcut tek alıntılara özel işlem yapmak zorundayız. Her zamanki gibi şöyle görünüyor:

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

Burada kabuğun yorumlayacağı ve birleştireceği dört dize vardır: ilk tek alıntı dize ( pgrep … awk), kaçan bir tek alıntı, tek alıntı awk programı, başka bir kaçan tek alıntı.

Elbette birçok alternatif var:

  • pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1} önemli olan her şeyden kaçmak
  • pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}Aynı, ancak gereksiz boşluk olmadan ( awk programında bile !)
  • "pgrep -fl java | grep -i datanode | awk '{print \$1}'" her şeyi çift alıntı, kaçış $
  • 'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"varyasyonunuz; kaçmak yerine (bir karakter) çift tırnak (iki karakter) kullanmaktan normal yoldan biraz daha uzun

İlk seviyede farklı fiyat teklifi kullanmak, bu seviyedeki diğer değişikliklere izin verir:

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

İlk değişkeni sudo / * su * komut satırına gömmek şunu verir:

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

Aynı dizeyi diğer herhangi bir tek kabuk seviyesi bağlamında (örn. ssh host …) Kullanabilirsiniz .

Sonra, üstüne bir ssh seviyesi eklediniz . Bu etkili bir başka kabuk seviyesidir: ssh komutun kendisini yorumlamaz, ancak uzak uçtaki bir kabuğa (örneğin (örneğin) sh -c …) verir ve bu kabuk dizeyi yorumlar.

ssh host 

İşlem aynıdır: dizeyi alın, bir alıntı yöntemi seçin, kullanın, gömün.

Tekrar tek tırnak işareti kullanmak:

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

Şimdi, yorumlanmış ve birleştirilen on bir dize var:, 'sudo su user -c 'kaçılmış tek fiyat teklifi, 'pgrep … awk 'kaçılmış tek alıntı, kaçış ters eğik çizgi, iki kaçış tek tırnak işareti, tek alıntı kâğıt programı, kaçılmış tek bir teklif, kaçış ters eğik çizgi ve son kaçış tek bir teklif .

Son form şöyle görünür:

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

Bu, elle yazmak için biraz hantaldır, ancak kabuğun tek tırnak işaretinin gerçek niteliği, hafif bir değişimi otomatikleştirmeyi kolaylaştırır:

#!/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
Harika bir açıklama!
Gilles 'SO- kötülük'

7

Genel bir çözümle ilgili net ve ayrıntılı bir açıklama için Chris Johnsen'in cevabına bakınız . Bazı yaygın durumlarda yardımcı olacak birkaç ekstra ipucu vereceğim.

Tek tırnak, tek bir alıntıdan başka her şeyden kaçar. Bu nedenle, bir değişkenin değerinin herhangi bir tek alıntı içermediğini biliyorsanız, bir kabuk komut dosyasındaki tek tırnak işaretleri arasında güvenle enterpolasyon yapabilirsiniz.

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

Yerel kabuğunuz ksh93 veya zsh ise, değişkene yeniden yazarak tek tırnak işaretleri ile baş edebilirsiniz '\''. (Her ne kadar bash ayrıca bir ${foo//pattern/replacement}yapıya sahip olsa da, tekli tırnakların kullanılması bana bir şey ifade etmiyor.)

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

Yuvalanmış alıntılarla uğraşmaktan kaçınmak için bir başka ipucu da mümkün olduğunca çevre değişkenleri içinden karakterleri geçirmektir. Ssh ve sudo ortam değişkenlerinin çoğunu bırakma eğilimindedir, ancak bunlar genellikle izin vermek için yapılandırılmıştır LC_*, çünkü bunlar normalde kullanılabilirlik için çok önemlidir (yerel bilgiler içerirler) ve nadiren güvenliğe duyarlı olarak kabul edilirler.

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

Burada, LC_CMDbir kabuk pasajı içerdiğinden, kelimenin tam anlamıyla en içteki kabuğa sağlanmalıdır. Bu nedenle değişken hemen üzerindeki kabuk tarafından genişler. En içteki-ama-bir kabuk görür "$LC_CMD"ve en içteki kabuk komutları görür.

Benzer bir yöntem, verileri bir metin işleme yardımcı programına aktarmak için kullanışlıdır. Kabuk enterpolasyonu kullanırsanız, yardımcı program değişkenin değerini bir komut olarak değerlendirir, örneğin sed "s/$pattern/$replacement/"değişkenler içeriyorsa çalışmaz /. Bu yüzden awk (sed değil) ve kabuğundan veri iletmek için -vseçenek ya da ENVIRONdiziyi kullanın (devam ederseniz ENVIRON, değişkenleri dışa aktarmayı unutmayın).

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

2

Chris Johnson'ın çok iyi tanımladığı gibi burada birkaç alıntılanmış aktarım seviyesine sahipsiniz; Yerel talimat size shelluzaktan talimat shellyoluyla sshbu talimat gerektiğini sudotalimat suuzaktan talimat shellsizin boru hattı çalıştırmak için pgrep -fl java | grep -i datanode | awk '{print $1}'olduğu gibi user. Bu tür bir komuta çok sıkıcı bir gereksinim vardır \'"quote quoting"\'.

Tavsiyeme uyursanız, saçmalığın hepsinden vazgeçip şunları yapacaksınız:

% 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>

DAHA FAZLASI İÇİN:

Bunun neden işe yaradığına dair teorinin bazılarını tartıştığım eğik çizgi ikame için farklı alıntı tiplerine sahip boru yolları (belki de çok uzun soluklu) cevabımı kontrol edin .

Mike


Bu ilginç görünüyor, ancak çalışmasını sağlayamıyorum. Minimal bir çalışma örneği gönderebilir misiniz?
John Lawrence Aspden,

Stdin için birden fazla yönlendirme kullandığından, bu örneğin zsh gerektirdiğine inanıyorum. Diğer Bourne benzeri mermilerde, ikincisi <<basitçe ilkini değiştirir. Bir yerde "sadece zsh" mi yazmalı yoksa bir şeyi mi özledim? (Yine de zekice bir numara, kısmen yerel genişlemeye maruz kalan bir sapkınlığa sahip olmak)

İşte bir bash uyumlu sürümü: unix.stackexchange.com/questions/422489/…
dabest1

0

Daha fazla çift tırnak kullanmaya ne dersiniz?

O zaman ssh $host $CMDbununla iyi çalışmalısın:

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

Şimdi daha karmaşık olana ssh $host "sudo su user -c \"$CMD\"". Ben de hassas karakterlerden kurtulmak yapmanız gereken tüm tahmin CMD: $, \ve ". Yani bunun işe yarayıp yaramadığını görmeyi denerdim echo $CMD | sed -e 's/[$\\"]/\\\1/g'.

O bakışlar OK ise yankı + bir kabuk işlevi içine sed kaydırmak ve gitmek iyidir ssh $host "sudo su user -c \"$(escape_my_var $CMD)\"".

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.