दूरस्थ उपयोगकर्ता के लॉगिन शेल को जाने बिना ssh पर एक मनमाना सरल कमांड कैसे निष्पादित करें?


26

ssh जब आप दौड़ते हैं तो उसमें एक कष्टप्रद विशेषता होती है:

ssh user@host cmd and "here's" "one arg"

इसके बजाय कि cmdअपने तर्कों पर चलने के बजाय host, यह cmdसमाप्‍त करता है और रिक्त स्थान के साथ तर्क करता है और hostपरिणामी स्ट्रिंग की व्याख्या करने के लिए एक शेल चलाता है (मुझे लगता है कि यही कारण है कि इसके कहा जाता है sshऔर नहीं sexec)।

इससे भी बदतर, आप नहीं जानते कि उस स्ट्रिंग की व्याख्या करने के लिए किस शेल का उपयोग किया जा रहा है क्योंकि यह लॉगिन शेल है userजिसकी बॉर्न होने की गारंटी भी नहीं है, क्योंकि अभी भी लोग tcshअपने लॉगिन शेल के रूप में उपयोग कर रहे हैं और fishबढ़ रहे हैं।

क्या इसके आसपास कोई रास्ता है?

मान लीजिए मैं एक में संग्रहीत तर्कों की सूची के रूप में एक आदेश है bashसरणी, जिनमें से प्रत्येक में गैर-शून्य बाइट्स की किसी भी क्रम हो सकती है, वहाँ उस पर मार डाला है करने के लिए किसी भी तरह से है hostके रूप में userइस बात का लॉगिन खोल की परवाह किए बिना एक सुसंगत तरीके से userपर host(जो हम मानेंगे कि एक प्रमुख यूनिक्स शैल परिवारों में से एक है: बॉर्न, csh, rc / es, मछली)?

एक और उचित धारणा जो मुझे बनाने में सक्षम होना चाहिए, वह यह है कि इसमें उपलब्ध एक shकमांड hostहै $PATHजो बॉर्न-संगत है।

उदाहरण:

cmd=(
  'printf'
  '<%s>\n'
  'arg with $and spaces'
  '' # empty
  $'even\n* * *\nnewlines'
  "and 'single quotes'"
  '!!'
)

मैं इसे स्थानीय स्तर पर चला सकते हैं के साथ ksh/ zsh/ bash/ yashके रूप में:

$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>

या

env "${cmd[@]}"

या

xterm -hold -e "${cmd[@]}"
...

मैं इसे कैसे पर चलाना शामिल है hostके रूप में userअधिक ssh?

ssh user@host "${cmd[@]}"

जाहिर है काम नहीं करेगा।

ssh user@host "$(printf ' %q' exec "${cmd[@]}")"

केवल तभी काम करेगा जब दूरस्थ उपयोगकर्ता का लॉगिन शेल स्थानीय शेल के समान था (या उसी तरह से उद्धृत करना समझता printf %qहै जैसे स्थानीय शेल इसे बनाता है) और उसी स्थान पर चलता है।


3
अगर cmdतर्क था कि /bin/sh -cहम सभी मामलों में 99% मामलों में एक पॉज़िक्स शेल के साथ समाप्त हो जाएंगे, तो क्या हम नहीं करेंगे? बेशक विशेष पात्रों से बचना इस तरह से थोड़ा अधिक दर्दनाक है, लेकिन क्या यह प्रारंभिक समस्या को हल करेगा?
बनगुंगिन

@Banganguin, नहीं, यदि आप चलाते हैं ssh host sh -c 'some cmd', उसी के रूप में ssh host 'sh -c some cmd', जिसमें दूरस्थ उपयोगकर्ता का लॉगिन शेल उस sh -c some cmdकमांड लाइन की व्याख्या करता है । हमें उस शेल के लिए सही सिंटैक्स में कमांड लिखने की आवश्यकता है (और हमें नहीं पता कि यह कौन है) ताकि इसे shवहाँ -cऔर some cmdतर्कों के साथ बुलाया जाए ।
स्टीफन चेज़लस

1
@ अन्य, हाँ, sh -c 'some cmd'और some cmdकमांड लाइन उन सभी गोले में समान हैं। अब क्या होगा अगर मैं echo \'रिमोट होस्ट पर बॉर्न कमांड लाइन चलाना चाहता हूं ? echo command-string | ssh ... /bin/shएक समाधान है जो मैंने अपने उत्तर में दिया था, लेकिन इसका मतलब है कि आप उस दूरस्थ कमांड के स्टड को डेटा नहीं दे सकते हैं।
स्टीफन चेज़ेलस

1
एक अधिक लंबे समय तक चलने वाले समाधान की तरह लगता है ssh के लिए एक rexec प्लगइन, ala ftp प्लगइन।
ओथियस

1
@ मेयरडाउन, नहीं, यह नहीं है, आपको शेल कमांड लाइन में तर्कों को अलग करने के लिए या तो स्थान या टैब की आवश्यकता है। यदि cmdहै cmd=(echo "foo bar"), तो शेल कमांड लाइन sshकुछ इस तरह होनी चाहिए जैसे `'इको' 'फू बार' . The *first* space (the one before इको ) is superflous, but doen't harm. The other one (the ones before बार 'फू बार' ) is needed. With '% q' , we'd pass a 'इको''फू बार' ' कमांड लाइन।
स्टीफन चेज़लस

जवाबों:


19

मुझे नहीं लगता कि sshशेल को शामिल किए बिना क्लाइंट से सर्वर तक एक कमांड को पारित करने के लिए किसी भी देशी तरीके का कोई भी कार्यान्वयन है ।

अब, चीजें आसान हो सकती हैं यदि आप दूरस्थ शेल को केवल एक विशिष्ट दुभाषिया चलाने के लिए कह सकते हैं (जैसे sh, जिसके लिए हम अपेक्षित सिंटैक्स जानते हैं) और कोड को किसी अन्य माध्यम से निष्पादित करने के लिए देते हैं।

यह अन्य मतलब उदाहरण के लिए हो सकता है मानक इनपुट या एक पर्यावरण चर

जब न तो उपयोग किया जा सकता है, मैं नीचे एक हैकी तीसरा समाधान प्रस्तावित करता हूं।

स्टड का उपयोग करना

यदि आपको दूरस्थ कमांड पर कोई डेटा फीड करने की आवश्यकता नहीं है, तो यह सबसे आसान उपाय है।

यदि आप जानते हैं कि रिमोट होस्ट में एक xargsकमांड है जो -0विकल्प का समर्थन करता है और कमांड बहुत बड़ी नहीं है, तो आप कर सकते हैं:

printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'

उस xargs -0 env --कमांड लाइन की व्याख्या उन सभी शेल परिवारों के साथ की जाती है। xargsस्टड पर तर्कों की शून्य-सीमांकित सूची को पढ़ता है और उन लोगों को तर्क के रूप में पास करता है env। यह पहला तर्क मानता है (कमांड नाम) में =वर्ण नहीं हैं ।

या आप सिंटैक्स shका उपयोग करके प्रत्येक तत्व को उद्धृत करने के बाद दूरस्थ होस्ट पर उपयोग कर सकते हैं sh

shquote() {
  LC_ALL=C awk -v q=\' '
    BEGIN{
      for (i=1; i<ARGC; i++) {
        gsub(q, q "\\" q q, ARGV[i])
        printf "%s ", q ARGV[i] q
      }
      print ""
    }' "$@"
}
shquote "${cmd[@]}" | ssh user@host sh

पर्यावरण चर का उपयोग करना

अब, यदि आपको क्लाइंट से दूरस्थ कमांड के स्टड पर कुछ डेटा फीड करने की आवश्यकता है, तो उपरोक्त समाधान काम नहीं करेगा।

कुछ sshसर्वर परिनियोजन हालांकि क्लाइंट से सर्वर पर मनमाने ढंग से पर्यावरण चर के पारित होने की अनुमति देते हैं। उदाहरण के लिए, डेबियन आधारित सिस्टम पर कई ओपनशूट परिनियोजन ऐसे चर को पार करने की अनुमति देते हैं जिनके नाम से शुरू होता है LC_

LC_CODEउदाहरण के लिए, आपके पास ऊपर के रूप में shquoted sh कोड वाले एक उदाहरण के लिए एक चर हो सकता है और sh -c 'eval "$LC_CODE"'दूरस्थ क्लाइंट पर उस चर को फिर से पास करने के लिए कहा जा सकता है (फिर से, यह एक कमांड-लाइन है जिसकी व्याख्या हर शेल में समान है):

LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
  sh -c '\''eval "$LC_CODE"'\'

सभी शेल परिवारों के लिए संगत कमांड लाइन का निर्माण

यदि उपरोक्त में से कोई भी विकल्प स्वीकार्य नहीं है (क्योंकि आपको स्टडिन और sshd की आवश्यकता है, किसी भी चर को स्वीकार नहीं करता है, या क्योंकि आपको एक सामान्य समाधान की आवश्यकता है), तो आपको दूरस्थ होस्ट के लिए एक कमांड लाइन तैयार करनी होगी जो सभी के साथ संगत हो समर्थित गोले।

यह विशेष रूप से मुश्किल है क्योंकि उन सभी गोले (बॉर्न, csh, आरसी, तों, मछली) का अपना अलग सिंटैक्स है, और विशेष रूप से अलग-अलग उद्धरण तंत्र में और उनमें से कुछ की सीमाएं हैं जो चारों ओर काम करना मुश्किल है।

यहाँ एक समाधान है जो मैं लेकर आया हूँ, मैं इसका और वर्णन करता हूँ:

#! /usr/bin/perl
my $arg, @ssh, $preamble =
q{printf '%.0s' "'\";set x=\! b=\\\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\\\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
};

@ssh = ('ssh');
while ($arg = shift @ARGV and $arg ne '--') {
  push @ssh, $arg;
}

if (@ARGV) {
  for (@ARGV) {
    s/'/'\$q\$b\$q\$q'/g;
    s/\n/'\$q'\$n'\$q'/g;
    s/!/'\$x'/g;
    s/\\/'\$b'/g;
    $_ = "\$q'$_'\$q";
  }
  push @ssh, "${preamble}exec sh -c 'IFS=;exec '" . join "' '", @ARGV;
}

exec @ssh;

कि perlचारों ओर एक आवरण लिपि है ssh। मैं इसे बुलाता हूं sexec। आप इसे कहते हैं:

sexec [ssh-options] user@host -- cmd and its args

आपके उदाहरण में:

sexec user@host -- "${cmd[@]}"

और आवरण cmd and its argsएक कमांड लाइन में बदल जाता है, जो सभी शेल cmdको अपने आर्ग्स (उनकी सामग्री के बावजूद) के साथ कॉल करने के रूप में व्याख्या करता है ।

सीमाएं:

  • प्रस्तावना और जिस तरह से कमांड का हवाला दिया जाता है मतलब दूरस्थ कमांड लाइन काफी बड़ा होता है जिसका मतलब है कि कमांड लाइन के अधिकतम आकार पर सीमा जल्द ही पहुंच जाएगी।
  • मैंने केवल इसके साथ परीक्षण किया है: बॉर्न शेल (हिरलूम टूलकेस्ट से), डैश, बैश, ज़श, mksh, lashh, यश, ksh93, rc, es, akanga, csh, tshsh, मछली जैसा हाल ही में डेबियन सिस्टम और / पर पाया गया। बिन / श, / usr / bin / ksh, / bin / csh और / usr / xpg4 / bin / sh Solaris 10 पर।
  • यदि yashदूरस्थ लॉगिन शेल है, तो आप एक कमांड पास नहीं कर सकते जिसके तर्कों में अमान्य वर्ण हैं, लेकिन यह एक सीमा है yashजिसमें आप किसी भी तरह से काम नहीं कर सकते।
  • Ssh पर आह्वान करने पर कुछ गोले जैसे csh या bash कुछ स्टार्टअप फाइलें पढ़ते हैं। हम मानते हैं कि उन लोगों ने व्यवहार को नाटकीय रूप से नहीं बदला ताकि प्रस्तावना अभी भी काम करे।
  • बगल में sh, यह भी मानता है कि रिमोट सिस्टम के पास printfकमांड है।

यह समझने के लिए कि यह कैसे काम करता है, आपको यह जानना होगा कि अलग-अलग गोले में कैसे काम करता है:

  • बॉर्न: '...'इसमें कोई विशेष चरित्र के साथ मजबूत उद्धरण हैं । "..."कमजोर उद्धरण हैं, जहां "बैकस्लैश से बचा जा सकता है।
  • csh। बॉर्न के रूप में भी सिवाय इसके कि "अंदर नहीं बचा जा सकता है "..."। साथ ही एक बैकलाइन के साथ उपसर्ग में एक नया वर्ण दर्ज करना होगा। और !सिंगल कोट्स के अंदर भी समस्याएं पैदा करता है।
  • rc। केवल उद्धरण '...'(मजबूत) हैं। एकल उद्धरण के भीतर एक एकल उद्धरण ''(जैसे '...''...') दर्ज किया गया है । डबल उद्धरण या बैकस्लैश विशेष नहीं हैं।
  • es। आरसी को उसी बाहरी उद्धरण के अलावा, बैकस्लैश एकल उद्धरण से बच सकते हैं।
  • fish: बॉर्न के समान ही कि बैकस्लैश 'अंदर से बच जाता है '...'

उन सभी विरोधाभासों के साथ, यह देखना आसान है कि कोई मज़बूती से कमांड लाइन तर्क नहीं दे सकता है ताकि यह सभी गोले के साथ काम करे।

के रूप में एकल उद्धरण का उपयोग:

'foo' 'bar'

सभी में काम करता है लेकिन:

'echo' 'It'\''s'

में काम नहीं करेगा rc

'echo' 'foo
bar'

में काम नहीं करेगा csh

'echo' 'foo\'

में काम नहीं करेगा fish

लेकिन हम अपने आसपास के लोगों की समस्याओं से ज्यादातर काम करने में सक्षम है, तो हम, चर में उन समस्याग्रस्त पात्रों स्टोर करने के लिए प्रबंधन में बैकस्लैश की तरह होना चाहिए $b, में एकल उद्धरण $qमें, न्यू लाइन $n(और !में $xएक खोल स्वतंत्र रास्ते में csh इतिहास के विस्तार के लिए)।

'echo' 'It'$q's'
'echo' 'foo'$b

सभी गोले में काम करेगा। हालांकि यह अभी भी newline के लिए काम नहीं करेगा csh। यदि $nनईलाइन शामिल है csh, तो, आपको $n:qइसे किसी नई पंक्ति में विस्तारित करने के लिए लिखना होगा और यह अन्य गोले के लिए काम नहीं करेगा। इसलिए, हम यहाँ क्या कर रहे हैं shऔर कॉल कर रहे हैं और shउन का विस्तार किया है $n। इसका मतलब यह भी है कि उद्धरण के दो स्तर करने के लिए, एक दूरस्थ लॉगिन शेल के लिए, और एक के लिएsh

$preambleकि कोड में trickiest हिस्सा है। यह (जब यह दूसरों के लिए बाहर टिप्पणी की है) सब गोले में विभिन्न विभिन्न हवाले से नियमों के उपयोग के गोले का केवल एक से व्याख्या कोड के कुछ वर्गों के लिए सिर्फ उन को परिभाषित करने, जिनमें से प्रत्येक में आता है $b, $q, $n,$x अपने-अपने खोल के लिए चर।

यहाँ शेल कोड है जो hostआपके उदाहरण के लिए दूरस्थ उपयोगकर्ता के लॉगिन शेल द्वारा व्याख्या किया जाएगा :

printf '%.0s' "'\";set x=\! b=\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
exec sh -c 'IFS=;exec '$q'printf'$q' '$q'<%s>'$b'n'$q' '$q'arg with $and spaces'$q' '$q''$q' '$q'even'$q'$n'$q'* * *'$q'$n'$q'newlines'$q' '$q'and '$q$b$q$q'single quotes'$q$b$q$q''$q' '$q''$x''$x''$q

जब कोई समर्थित गोले द्वारा व्याख्या की जाती है तो वह कोड उसी कमांड को चलाता है।


1
SSH प्रोटोकॉल ( RFC 4254 H6.5 ) एक रिमोट कमांड को एक स्ट्रिंग के रूप में परिभाषित करता है। यह उस स्ट्रिंग की व्याख्या करने का तरीका तय करने के लिए सर्वर पर निर्भर है। यूनिक्स सिस्टम पर, सामान्य व्याख्या उपयोगकर्ता के लॉगिन शेल को स्ट्रिंग पास करना है। एक प्रतिबंधित खाते के लिए, यह rssh या भीड़ जैसा कुछ हो सकता है जो मनमाना आदेशों को स्वीकार नहीं करता है। यहां तक ​​कि खाते पर या उस कुंजी पर एक मजबूर कमांड भी हो सकता है जो क्लाइंट द्वारा भेजे गए कमांड स्ट्रिंग को अनदेखा करने का कारण बनता है।
गिल्स एसओ- बुराई को रोकना '25

1
@ गिल्स, RFC संदर्भ के लिए धन्यवाद। हां, इस प्रश्नोत्तर के लिए धारणा यह है कि दूरस्थ उपयोगकर्ता का लॉगिन शेल प्रयोग करने योग्य है (जैसा कि मैं उस दूरस्थ कमांड को चलाना चाहता हूं) और पोसिक्स सिस्टम पर मुख्य शेल परिवारों में से एक है। मुझे प्रतिबंधित गोले या गैर-गोले या बल आदेशों या ऐसी किसी भी चीज़ में दिलचस्पी नहीं है जो मुझे वैसे भी उस दूरस्थ कमांड को चलाने की अनुमति नहीं देगी।
स्टीफन चेज़लस

1
हाइपरपॉलीग्लॉट में कुछ सामान्य गोले के बीच वाक्यविन्यास में मुख्य अंतर के बारे में एक उपयोगी संदर्भ पाया जा सकता है ।
lcd047

0

tl; डॉ

ssh USER@HOST -p PORT $(printf "%q" "cmd") $(printf "%q" "arg1") \
    $(printf "%q" "arg2")

अधिक विस्तृत समाधान के लिए, टिप्पणियों को पढ़ें और अन्य उत्तर का निरीक्षण करें

विवरण

खैर, मेरा समाधान गैर- bashगोले के साथ काम नहीं करेगा । लेकिन यह bashदूसरे छोर पर है, यह मानते हुए कि चीजें सरल हो जाती हैं। मेरा विचार printf "%q"भागने के लिए पुन: उपयोग करना है। आम तौर पर, दूसरे छोर पर एक स्क्रिप्ट रखना अधिक पठनीय होता है, जो तर्कों को स्वीकार करता है। लेकिन अगर कमांड छोटा है, तो संभवत: इसे इनलाइन करना ठीक है। लिपियों में उपयोग करने के लिए कुछ उदाहरण कार्य हैं:

local.sh:

#!/usr/bin/env bash
set -eu

ssh_run() {
    local user_host_port=($(echo "$1" | tr '@:' ' '))
    local user=${user_host_port[0]}
    local host=${user_host_port[1]}
    local port=${user_host_port[2]-22}
    shift 1
    local cmd=("$@")
    local a qcmd=()
    for a in ${cmd[@]+"${cmd[@]}"}; do
        qcmd+=("$(printf "%q" "$a")")
    done
    ssh "$user"@"$host" -p "$port" ${qcmd[@]+"${qcmd[@]}"}
}

ssh_cmd() {
    local user_host_port=$1
    local cmd=$2
    shift 2
    local args=("$@")
    ssh_run "$user_host_port" bash -lc "$cmd" - ${args[@]+"${args[@]}"}
}

ssh_run USER@HOST ./remote.sh "1  '  \"  2" '3  '\''  "  4'
ssh_cmd USER@HOST:22 "for a; do echo \"'\$a'\"; done" "1  '  \"  2" '3  '\''  "  4'
ssh_cmd USER@HOST:22 'for a; do echo "$a"; done' '1  "2' "3'  4"

remote.sh:

#!/usr/bin/env bash
set -eu
for a; do
    echo "'$a'"
done

उत्पादन:

'1  '  "  2'
'3  '  "  4'
'1  '  "  2'
'3  '  "  4'
1  "2
3'  4

वैकल्पिक रूप से, आप printfस्वयं कार्य कर सकते हैं , यदि आप जानते हैं कि आप क्या कर रहे हैं:

ssh USER@HOST ./1.sh '"1  '\''  \"  2"' '"3  '\''  \"  4"'

1
यह मान लेता है कि रिमोट उपयोगकर्ता का लॉगिन शेल बैश है (बैश के प्रिंटफ% q भावों को बैश फैशन में) और वह bashरिमोट मशीन पर उपलब्ध है। गुम उद्धरणों के साथ कुछ समस्याएं भी हैं जो व्हॉट्सएप और वाइल्डकार्ड के साथ समस्याएं पैदा करती हैं।
स्टीफन चेज़लस

@ स्टीफनचेज़ेलस दरअसल, मेरा समाधान शायद केवल bashगोले को लक्षित करना है । लेकिन उम्मीद है कि लोग इसका इस्तेमाल करेंगे। मैंने हालांकि अन्य मुद्दों को संबोधित करने की कोशिश की। अगर कोई चीज मुझे याद आ रही है, तो मुझे यह बताने में संकोच न करें bash
x- यूरी

1
ध्यान दें कि यह अभी भी प्रश्न में नमूना कमांड के साथ काम नहीं करता है ( ssh_run user@host "${cmd[@]}")। आपके पास अभी भी कुछ गायब उद्धरण हैं।
स्टीफन चेजालस

1
वह बेहतर है। ध्यान दें कि बैश का आउटपुट printf %qअलग-अलग लोकेल में उपयोग करने के लिए सुरक्षित नहीं है (और यह भी काफी छोटी है, उदाहरण के लिए, BIG5 चारसेट का उपयोग करने वाले स्थानों में, यह (4.3.48) उद्धरण के εरूप में α`!)। उसके लिए, सबसे अच्छा यह है कि shquote()मैं अपने जवाब के साथ सब कुछ और केवल उद्धरण के साथ बोलूं।
स्टीफन चेज़लस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.