कैसे सिर्फ बैश और कुछ नहीं (कोई कर्ल, wget, पर्ल, आदि) का उपयोग कर एक फ़ाइल डाउनलोड करने के लिए


40

मैं एक न्यूनतम नेतृत्वहीन * nix जो है नहीं है डाउनलोड फ़ाइलें (जैसे कोई कर्ल, wget, आदि) के लिए किसी भी कमांड लाइन उपयोगिताओं। मेरे पास केवल बैश है।

मैं एक फ़ाइल कैसे डाउनलोड कर सकता हूं?

आदर्श रूप से, मैं एक समाधान चाहूंगा जो * निक्स की एक विस्तृत श्रृंखला में काम करेगा।


कैसे के बारे मेंgawk
नील मैकग्विन

मुझे अब याद नहीं है कि अगर गॉव उपलब्ध था, हालांकि मैं एक गॉव आधारित समाधान देखना पसंद करूंगा यदि आपके पास एक है :)
क्रिस स्नो

1
यहाँ एक उदाहरण है: gnu.org/software/gawk/manual/gawkinet/gawkinet.html#Web-page
नील

जवाबों:


64

यदि आपके पास 2.04 या उससे ऊपर का /dev/tcpछद्म उपकरण सक्षम है, तो आप bash से ही कोई फ़ाइल डाउनलोड कर सकते हैं।

निम्नलिखित कोड को सीधे बैश शेल में पेस्ट करें (आपको निष्पादन के लिए कोड को फ़ाइल में सहेजने की आवश्यकता नहीं है):

function __wget() {
    : ${DEBUG:=0}
    local URL=$1
    local tag="Connection: close"
    local mark=0

    if [ -z "${URL}" ]; then
        printf "Usage: %s \"URL\" [e.g.: %s http://www.google.com/]" \
               "${FUNCNAME[0]}" "${FUNCNAME[0]}"
        return 1;
    fi
    read proto server path <<<$(echo ${URL//// })
    DOC=/${path// //}
    HOST=${server//:*}
    PORT=${server//*:}
    [[ x"${HOST}" == x"${PORT}" ]] && PORT=80
    [[ $DEBUG -eq 1 ]] && echo "HOST=$HOST"
    [[ $DEBUG -eq 1 ]] && echo "PORT=$PORT"
    [[ $DEBUG -eq 1 ]] && echo "DOC =$DOC"

    exec 3<>/dev/tcp/${HOST}/$PORT
    echo -en "GET ${DOC} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3
    while read line; do
        [[ $mark -eq 1 ]] && echo $line
        if [[ "${line}" =~ "${tag}" ]]; then
            mark=1
        fi
    done <&3
    exec 3>&-
}

फिर आप इसे शेल से निम्नानुसार निष्पादित कर सकते हैं:

__wget http://example.iana.org/

स्रोत: मॉर्गिनी कमांड लाइन के माध्यम से मोरकी के उन्नयन और पैकेज स्थापित करना?

अपडेट: जैसा कि टिप्पणी में बताया गया है, ऊपर उल्लिखित दृष्टिकोण सरलीकृत है:

  • readवसीयत बैकलैश और प्रमुख व्हाट्सएप को मिटा देगा।
  • बैश NUL बाइट्स के साथ बहुत अच्छी तरह से निपट नहीं सकते हैं ताकि बाइनरी फाइलें बाहर हो जाएं।
  • अयोग्य घोषित कर दिया $lineजाएगा।

8
तो आपने अपने प्रश्न का उसी समय उत्तर दिया जैसा आपने पूछा था। यह आपके लिए एक दिलचस्प समय मशीन है;)
मीर बोर्ग

11
@MeBBorg - जब आप कोई प्रश्न पूछते हैं, तो टिक बॉक्स की तलाश करें 'अपने खुद के प्रश्न का उत्तर दें' - blog.stackoverflow.com/2011/07/…
क्रिस स्नो

@eestartup - मुझे नहीं लगता कि आप अपने जवाब के लिए वोट कर सकते हैं। क्या मैं कोड समझा सकता हूं? अभी नहीं! लेकिन यह साइबरविन पर काम करता है।
क्रिस स्नो

3
बस एक नोट: यह बैश के कुछ विन्यास के साथ काम नहीं करेगा। मेरा मानना ​​है कि डेबियन बैश के वितरण से इस सुविधा को कॉन्फ़िगर करता है।

1
उरग, जबकि यह एक अच्छी चाल है, यह भी आसानी से भ्रष्ट डाउनलोड का कारण बन सकता है। while readजैसे कि बैकस्लैश और प्रमुख व्हाट्सएप को ट्रैश करता है और बैश NUL बाइट्स के साथ बहुत अच्छी तरह से निपट नहीं सकता है ताकि बाइनरी फाइलें बाहर हो जाएं। और निर्विवाद रूप $lineसे ग्लोब होगा ... इसका कोई भी जवाब मुझे नहीं मिला।
इल्काचू

19

लिनेक्स का प्रयोग करें।

यह अधिकांश यूनिक्स / लिनक्स के लिए बहुत आम है।

lynx -dump http://www.google.com

-dump: स्टडआउट और बाहर निकलने के लिए पहली फ़ाइल को डंप करें

man lynx

या netcat:

/usr/bin/printf 'GET / \n' | nc www.google.com 80

या टेलनेट:

(echo 'GET /'; echo ""; sleep 1; ) | telnet www.google.com 80

5
ओपी में "* निक्स है, जिसमें फ़ाइलों को डाउनलोड करने के लिए कोई कमांड लाइन उपयोगिताओं नहीं है", इसलिए सुनिश्चित करने के लिए कोई लिंक्स नहीं है।
सेलडा

2
नोट lynx -sourcewget के करीब है
स्टीवन पेनी

अरे, तो यह वास्तव में देर से टिप्पणी है, लेकिन आप टेलनेट कमांड के आउटपुट को किसी फ़ाइल में कैसे सहेजते हैं? ">" के साथ पुनर्निर्देशित करना फ़ाइल की सामग्री और टेलनेट आउटपुट दोनों को आउटपुट करता है, जैसे "93.184.216.34 को आज़माना ... www.example.com से जुड़ा।" मैं ऐसी स्थिति में हूं जहां मैं केवल टेलनेट का उपयोग कर सकता हूं, मैं कम से कम चौखटे के साथ चेरोट जेल बनाने की कोशिश कर रहा हूं।
पिक्सेलोमर

10

क्रिस स्नो उत्तर से अनुकूलित यह बाइनरी ट्रांसफर फ़ाइलों को भी संभाल सकता है

function __curl() {
  read proto server path <<<$(echo ${1//// })
  DOC=/${path// //}
  HOST=${server//:*}
  PORT=${server//*:}
  [[ x"${HOST}" == x"${PORT}" ]] && PORT=80

  exec 3<>/dev/tcp/${HOST}/$PORT
  echo -en "GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n" >&3
  (while read line; do
   [[ "$line" == $'\r' ]] && break
  done && cat) <&3
  exec 3>&-
}
  • मैं पढ़ने से बाहर निकलने के लिए && बिल्ली तोड़ता हूं
  • मैं http 1.0 का उपयोग करता हूं, इसलिए कनेक्शन के लिए प्रतीक्षा करने / भेजने की कोई आवश्यकता नहीं है: करीब

आप इस तरह से बाइनरी फ़ाइलों का परीक्षण कर सकते हैं

ivs@acsfrlt-j8shv32:/mnt/r $ __curl http://www.google.com/favicon.ico > mine.ico
ivs@acsfrlt-j8shv32:/mnt/r $ curl http://www.google.com/favicon.ico > theirs.ico
ivs@acsfrlt-j8shv32:/mnt/r $ md5sum mine.ico theirs.ico
f3418a443e7d841097c714d69ec4bcb8  mine.ico
f3418a443e7d841097c714d69ec4bcb8  theirs.ico

यह बाइनरी ट्रांसफ़र फ़ाइलों को हैंडल नहीं करेगा - यह नल बाइट्स पर विफल हो जाएगा।
वाइल्डकार्ड

@Wildcard, मुझे समझ नहीं आ रहा है, मैंने एक बाइनरी फाइल ट्रांसफर उदाहरण (नल बाइट्स युक्त) के साथ संपादित किया है, क्या आप मुझे इंगित कर सकते हैं कि मैं क्या याद कर रहा हूं?
131

2
@Wildcard, heheh, हाँ ऐसा लगता है कि यह काम करना चाहिए, क्योंकि यह वास्तविक फ़ाइल डेटा को पढ़ता है cat। मुझे यकीन नहीं है कि यह धोखा है (क्योंकि यह विशुद्ध रूप से शेल नहीं है), या एक अच्छा समाधान (चूंकि catएक मानक उपकरण है, आखिरकार)। लेकिन @ 131, आप इस बारे में एक नोट जोड़ना चाह सकते हैं कि यह अन्य समाधानों से बेहतर क्यों है।
ilkkachu

@Wildcard, मैंने नीचे दिए गए जवाब के रूप में शुद्ध बैश समाधान भी जोड़ा। और हाँ, धोखा देना या न देना, यह एक वैध उपाय है और एक
उभार के

7

" बस बैश और कुछ नहीं " को सख्ती से लेते हुए , यहां पहले के उत्तरों ( @ क्रिस , @ 131 ) का एक अनुकूलन है जो किसी भी बाहरी उपयोगिताओं को नहीं बुलाता है (मानक भी नहीं) लेकिन बाइनरी फ़ाइलों के साथ भी काम करता है:

#!/bin/bash
download() {
  read proto server path <<< "${1//"/"/ }"
  DOC=/${path// //}
  HOST=${server//:*}
  PORT=${server//*:}
  [[ x"${HOST}" == x"${PORT}" ]] && PORT=80

  exec 3<>/dev/tcp/${HOST}/$PORT

  # send request
  echo -en "GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n" >&3

  # read the header, it ends in a empty line (just CRLF)
  while IFS= read -r line ; do 
      [[ "$line" == $'\r' ]] && break
  done <&3

  # read the data
  nul='\0'
  while IFS= read -d '' -r x || { nul=""; [ -n "$x" ]; }; do 
      printf "%s$nul" "$x"
  done <&3
  exec 3>&-
}

के साथ प्रयोग करें download http://path/to/file > file

हम एनयूएल बाइट्स के साथ सौदा करते हैं read -d ''। यह एक एनयूएल बाइट तक पढ़ता है, और अगर यह एक नहीं मिला, तो यह सच है कि अगर यह नहीं मिला, तो यह सही है। बैश स्ट्रिंग्स में एनयूएल बाइट्स को संभाल नहीं सकते हैं, इसलिए जब readसच के साथ रिटर्न होता है, तो हम प्रिंटिंग करते समय मैन्युअल रूप से एनयूएल बाइट जोड़ते हैं, और जब यह गलत हो जाता है, तो हम जानते हैं कि कोई एनयूएल बाइट्स नहीं हैं और यह डेटा का अंतिम टुकड़ा होना चाहिए। ।

के साथ बीच में NULs साथ फाइलों पर बैश 4.4 के साथ परीक्षण किया गया, और शून्य, एक या दो NULs में समाप्त होने वाले हैं, और भी wgetऔर curlडेबियन से बाइनरी। 373 kB wgetबाइनरी को डाउनलोड करने में लगभग 5.7 सेकंड का समय लगा। लगभग 65 kB / s की गति या 512 kb / s से थोड़ा अधिक।

इसकी तुलना में, @ 131 का कैट-सॉल्यूशन 0.1 एस से कम या लगभग सौ गुना तेज है। बहुत आश्चर्य की बात नहीं, वास्तव में।

यह स्पष्ट रूप से मूर्खतापूर्ण है, क्योंकि बाहरी उपयोगिताओं के बिना, हम डाउनलोड की गई फ़ाइल के साथ बहुत कुछ नहीं कर सकते हैं, इसे निष्पादन योग्य भी नहीं बनाते हैं।


क्या एक स्टैंडअलोन-गन-बाइनरी गूंज नहीं है? (: पी)
131

1
@ १३१, नहीं! बैश है echoऔर printfprintfprintf -v
बिलिंस के

4

यदि आपके पास यह पैकेज libwww-perl है

आप बस का उपयोग कर सकते हैं:

/usr/bin/GET

यह देखते हुए कि अन्य उत्तर प्रश्न की आवश्यकता का सम्मान नहीं करते हैं (केवल मारना), मुझे लगता है कि यह वास्तव में lynxसमाधान से बेहतर है , क्योंकि पर्ल निश्चित रूप से उस लिंक्स की पूर्व स्थापना की अधिक संभावना है।
मार्कस

4

इसके बजाय अपने स्थानीय मशीन से SSH के माध्यम से अपलोड करने का उपयोग करें

एक "न्यूनतम हेडलेस * निक्स" बॉक्स का मतलब है कि आप शायद इसमें एसएसएच। तो आप इसे अपलोड करने के लिए SSH का भी उपयोग कर सकते हैं । जो कार्यात्मक रूप से डाउनलोड करने के बराबर है (सॉफ्टवेयर पैकेजों के आदि) को छोड़कर जब आप डाउनलोड कमांड को अपने हेडलेस सर्वर पर स्क्रिप्ट में शामिल करना चाहते हैं।

जैसा कि इस उत्तर में दिखाया गया है , आप अपने रिमोट हेडलेस सर्वर पर फ़ाइल रखने के लिए अपनी स्थानीय मशीन पर निम्नलिखित कार्य को अंजाम देंगे :

wget -O - http://example.com/file.zip | ssh user@host 'cat >/path/to/file.zip'

तीसरी मशीन से SSH के माध्यम से अपलोड करने वाला तेज़

डाउनलोड करने की तुलना में उपरोक्त समाधान का नुकसान कम स्थानांतरण गति है, क्योंकि आपके स्थानीय मशीन के साथ कनेक्शन में आमतौर पर आपके हेडलेस सर्वर और अन्य सर्वर के बीच कनेक्शन की तुलना में बहुत कम बैंडविड्थ होता है।

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

सुरक्षित होने के लिए, उस कमांड को कॉपी करें और पेस्ट करें जिसमें प्रमुख स्पेस कैरेक्टर भी शामिल हो ' ' । कारण के लिए नीचे दिए गए स्पष्टीकरण देखें।

 ssh user@intermediate-host "sshpass -f <(printf '%s\n' yourpassword) \
   ssh -T -e none \
     -o StrictHostKeyChecking=no \
     < <(wget -O - http://example.com/input-file.zip) \
     user@target-host \
     'cat >/path/to/output-file.zip' \
"

स्पष्टीकरण:

  • कमांड आपकी तीसरी मशीन को भेज देगी intermediate-host, एक फाइल को वहां से डाउनलोड करना शुरू कर देगी wget, और उसे target-hostSSH के माध्यम से अपलोड करना शुरू कर देगी । डाउनलोड करना और अपलोड करना आपके बैंडविड्थ का उपयोग करता है intermediate-hostऔर एक ही समय में होता है (बैश पाइप समकक्षों के कारण), इसलिए प्रगति तेज होगी।

  • इसका उपयोग करते समय, आपको दो सर्वर लॉगिन ( user@*-host), लक्ष्य होस्ट पासवर्ड ( yourpassword), डाउनलोड URL ( http://example.com/…) और आउटपुट पथ अपने लक्ष्य होस्ट ( /path/to/output-file.zip) पर उचित मानों के साथ प्रतिस्थापित करना होगा।

  • के लिए -T -e noneSSH विकल्प जब यह फ़ाइलों को हस्तांतरण करने का उपयोग कर, देख इन विस्तृत व्याख्या

  • यह कमांड उन मामलों के लिए है जहां आप SSH के सार्वजनिक कुंजी प्रमाणीकरण तंत्र का उपयोग नहीं कर सकते हैं - यह अभी भी कुछ साझा होस्टिंग प्रदाताओं के साथ होता है, विशेष रूप से मेजबान यूरोप । अभी भी प्रक्रिया को स्वचालित करने के लिए, हम sshpassकमांड में पासवर्ड की आपूर्ति करने में सक्षम होने के लिए भरोसा करते हैं। इसे sshpassआपके मध्यवर्ती होस्ट ( sudo apt-get install sshpassउबंटू के तहत) पर स्थापित करने की आवश्यकता है ।

  • हम sshpassएक सुरक्षित तरीके से उपयोग करने की कोशिश करते हैं , लेकिन यह अभी भी SSH प्यूबिक मैकेनिज्म (कहते हैं man sshpass) जितना सुरक्षित नहीं होगा । विशेष रूप से, हम SSH पासवर्ड को कमांड लाइन तर्क के रूप में नहीं, बल्कि एक फ़ाइल के माध्यम से आपूर्ति करते हैं, जिसे bash प्रक्रिया प्रतिस्थापन द्वारा प्रतिस्थापित किया जाता है ताकि यह सुनिश्चित हो सके कि यह डिस्क पर मौजूद नहीं है। printfएक पार्टी में निर्मित, यकीन है कि कोड के इस हिस्से में एक अलग आदेश के रूप में पॉप नहीं करता है अप में बना रही है psआउटपुट के रूप में है कि पासवर्ड [बेनकाब होगा स्रोत ]। मुझे लगता है कि इसका उपयोग sshpassवैसा ही सुरक्षित है जैसा कि sshpass -d<file-descriptor>वैरिएंट में सुझाया गया है man sshpass, क्योंकि बैश /dev/fd/*वैसे भी इस तरह के फाइल डिस्क्रिप्टर में आंतरिक रूप से मैप करता है। और वह एक अस्थायी फ़ाइल [ स्रोत का उपयोग किए बिना]। लेकिन कोई गारंटी नहीं, शायद मैंने कुछ अनदेखी की।

  • sshpassउपयोग को सुरक्षित बनाने के लिए , हमें कमांड को आपके स्थानीय मशीन पर बैश इतिहास में रिकॉर्ड होने से रोकने की आवश्यकता है। उसके लिए, पूरे कमांड को एक अंतरिक्ष वर्ण के साथ रखा गया है, जिसका यह प्रभाव है।

  • -o StrictHostKeyChecking=noभाग मामले में यह लक्ष्य मेजबान से जुड़ा कभी नहीं में असफल होने से आदेश को रोकता है। (आम तौर पर, SSH तब कनेक्शन के प्रयास की पुष्टि करने के लिए उपयोगकर्ता इनपुट की प्रतीक्षा करेगा। हम इसे वैसे भी आगे बढ़ाते हैं।)

  • sshpassअपने अंतिम तर्क के रूप में एक sshया scpकमांड की अपेक्षा करता है । तो हमें wget -O - … | ssh …एक कमांड को बैश पाइप के बिना एक फॉर्म में फिर से लिखना होगा , जैसा कि यहां बताया गया है


3

@ क्रिस बर्फ नुस्खा पर आधारित है। मैंने कुछ सुधार किए:

  • http योजना की जाँच (यह केवल http का समर्थन करती है)
  • http प्रतिक्रिया सत्यापन (प्रतिक्रिया स्थिति लाइन की जाँच करें, और हेडर और बॉडी को '\ r \ n' लाइन से विभाजित करें, न कि 'कनेक्शन: क्लोज़' जो कभी-कभी सच नहीं है)
  • गैर-200 कोड पर विफल (इंटरनेट पर फ़ाइलों को डाउनलोड करना महत्वपूर्ण है)

यहाँ कोड है:

function __wget() {
    : ${DEBUG:=0}
    local URL=$1
    local tag="Connection: close"

    if [ -z "${URL}" ]; then
        printf "Usage: %s \"URL\" [e.g.: %s http://www.google.com/]" \
               "${FUNCNAME[0]}" "${FUNCNAME[0]}"
        return 1;
    fi  
    read proto server path <<<$(echo ${URL//// })
    local SCHEME=${proto//:*}
    local PATH=/${path// //} 
    local HOST=${server//:*}
    local PORT=${server//*:}
    if [[ "$SCHEME" != "http" ]]; then
        printf "sorry, %s only support http\n" "${FUNCNAME[0]}"
        return 1
    fi  
    [[ x"${HOST}" == x"${PORT}" ]] && PORT=80
    [[ $DEBUG -eq 1 ]] && echo "SCHEME=$SCHEME" >&2
    [[ $DEBUG -eq 1 ]] && echo "HOST=$HOST" >&2
    [[ $DEBUG -eq 1 ]] && echo "PORT=$PORT" >&2
    [[ $DEBUG -eq 1 ]] && echo "PATH=$PATH" >&2

    exec 3<>/dev/tcp/${HOST}/$PORT
    if [ $? -ne 0 ]; then
        return $?
    fi  
    echo -en "GET ${PATH} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3
    if [ $? -ne 0 ]; then
        return $?
    fi  
    # 0: at begin, before reading http response
    # 1: reading header
    # 2: reading body
    local state=0
    local num=0
    local code=0
    while read line; do
        num=$(($num + 1))
        # check http code
        if [ $state -eq 0 ]; then
            if [ $num -eq 1 ]; then
                if [[ $line =~ ^HTTP/1\.[01][[:space:]]([0-9]{3}).*$ ]]; then
                    code="${BASH_REMATCH[1]}"
                    if [[ "$code" != "200" ]]; then
                        printf "failed to wget '%s', code is not 200 (%s)\n" "$URL" "$code"
                        exec 3>&-
                        return 1
                    fi
                    state=1
                else
                    printf "invalid http response from '%s'" "$URL"
                    exec 3>&-
                    return 1
                fi
            fi
        elif [ $state -eq 1 ]; then
            if [[ "$line" == $'\r' ]]; then
                # found "\r\n"
                state=2
            fi
        elif [ $state -eq 2 ]; then
            # redirect body to stdout
            # TODO: any way to pipe data directly to stdout?
            echo "$line"
        fi
    done <&3
    exec 3>&-
}

अच्छा संवर्द्धन +1
क्रिस स्नो

यह काम किया, लेकिन मुझे एक चिंता मिली, जब मैं इस स्क्रिप्ट का उपयोग करता हूं, यह कई सेकंड प्रतीक्षा करता है जब सभी डेटा समाप्त हो जाते हैं, तो यह मामला @Chris स्नो उत्तर में नहीं होता है, कोई भी इसे समझा सकता है?
zw963

और, इस जवाब में, echo -en "GET ${PATH} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3, ${tag}निर्दिष्ट नहीं है।
zw963

मैं tagचर के साथ इस उत्तर को सही सेट करता हूं , यह अब अच्छी तरह से काम करता है।
zw963

zsh के साथ काम नहीं कर रहा, __wget google.com क्षमा करें, केवल http / usr / bin / env: bash का समर्थन करें: ऐसी कोई फ़ाइल या निर्देशिका नहीं
vrkansagara
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.