sadece bash ve başka hiçbir şey kullanarak nasıl dosya indirilir (curl, wget, perl, vs.)


40

Dosyaları indirmek için herhangi bir komut satırı yardımcı programına sahip olmayan (örn. Curl, wget vb.) Çok az bir başlıksız * nix'im var . Bende sadece bash var.

Bir dosyayı nasıl indirebilirim?

İdeal olarak, geniş bir yelpazede * nix'te çalışacak bir çözüm istiyorum.


gawk
Peki

Gawk müsait olup olmadığını şimdi hatırlayamıyorum, bir tane varsa gawk tabanlı bir çözüm görmeyi çok isterdim :)
Chris Snow

Yanıtlar:


64

Eğer /dev/tcpsözde cihaz etkinken bash 2.04 veya üzeri bir sürüm kullanıyorsanız, bir dosyayı bash'ın kendisinden indirebilirsiniz.

Aşağıdaki kodu doğrudan bir bash kabuğuna yapıştırın (yürütmek için kodu bir dosyaya kaydetmeniz gerekmez):

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>&-
}

Sonra kabuğundan aşağıdaki gibi çalıştırabilirsiniz:

__wget http://example.iana.org/

Kaynak: Moreaki'nin cevabı cygwin komut satırı üzerinden paketleri yükseltme ve yükleme?

Güncelleme: Yorumda belirtildiği gibi, yukarıda belirtilen yaklaşım basittir:

  • readirade tersbölüleri ve lider boşluk trashes.
  • Bash, NUL baytlarıyla çok iyi başa çıkamaz, bu yüzden ikili dosyalar çıkarılır.
  • alıntı $lineyapılmaz.

8
Demek istediğin gibi kendi soruna cevap verdin. Bu ilginç bir zaman makinesine sahipsin;)
Meer Borg

11
@MeerBorg - Bir soru sorduğunuzda, 'kendi sorunuza cevap verin' onay kutusunu arayın - blog.stackoverflow.com/2011/07/…
Chris Snow

@eestartup - Kendi cevabın için oy verebileceğini sanmıyorum. Kodu açıklayabilir miyim? Henüz değil! Ancak cygwin üzerinde çalışır.
Chris Snow,

3
Sadece bir not: Bu, Bash'in bazı konfigürasyonlarıyla çalışmaz. Debian'ın bu özelliği Bash'in dağıtımından yapılandırdığını düşünüyorum.

1
Urgh, bu güzel bir numara olsa da, kolayca indirilen dosyalara neden olabilir. while readÖyle bir çöp tenekeleri ters eğik çizgiler ve önde gelen boşluk ve Bash NUL bayt ile çok güzel baş edemez bu yüzden ikili dosyalar çıktı. Ve alıntı $lineyapılmayacak kadar zorlayacak ... Bunların hiçbiri cevabında bahsettiğimi görmedim.
ilkkachu

19

Lynx kullanın.

Unix / Linux'un çoğu için oldukça yaygındır.

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

-dump: ilk dosyayı stdout'a döküp çıkar

man lynx

Veya netcat:

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

Veya telnet:

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

5
OP'de "dosyaları indirmek için herhangi bir komut satırı yardımcı programı bulunmayan nix" var, bu yüzden kesinlikle lynx yok.
Celada

2
Not lynx -sourcewget daha yaklaştı
Steven Penny

Hey, bu gerçekten çok geç bir yorum ama telnet komutunun çıktısını bir dosyaya nasıl kaydedersiniz? ">" İle yönlendirmek, hem dosyanın içeriğini hem de "93.184.216.34 ... www.example.com sitesine bağlandı." Gibi telnet çıktısını verir. Sadece telnet kullanabileceğim bir durumdayım, mümkün olan en az çerçeveye sahip chroot hapishanesi yapmaya çalışıyorum.
pikselomer

10

Chris Snow'un cevaplarından uyarlanmıştır Bu, ikili transfer dosyalarını da idare edebilir.

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>&-
}
  • okudum için kedileri kırdım
  • http 1.0 kullanıyorum, bu yüzden bir bağlantıyı beklemeye / göndermeye gerek yok: kapat

Bunun gibi ikili dosyaları test edebilirsiniz

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

Bu ikili aktarım dosyalarını işlemeyecek — boş baytlarda başarısız olacaktır.
Wildcard

@Wildcard, anlamıyorum, bir ikili dosya aktarımı örneğiyle (boş baytlar içeren) düzenleme yaptım, eksik olduğumu gösterebilir misiniz?
131

2
@Wildcard, heheh, evet, asıl dosya verilerini okuduğu için çalışması gerektiği gibi görünüyor cat. Aldatma olup olmadığından emin değilim (çünkü tamamen kabuk değil ) veya güzel bir çözüm ( catsonuçta standart bir araç olduğu için). Ancak @ 131, neden buradaki diğer çözümlerden daha iyi çalıştığı hakkında bir not eklemek isteyebilirsiniz.
ilkkachu

@Wildcard, saf bash çözümünü de bir cevap olarak ekledim. Ve evet, aldatma ya da almama, bu geçerli bir çözüm ve değer kazanmaya değer :)
ilkkachu

7

Kesinlikle " sadece Bash ve başka hiçbir şey " ibaresini alarak, daha önceki cevapların ( @ Chris'in , @ 131'sinin ) herhangi bir harici yardımcı programı (standart olanları bile değil) çağıran, aynı zamanda ikili dosyalarla çalışan bir uyarlaması :

#!/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>&-
}

İle kullanın download http://path/to/file > file.

NUL baytlarıyla anlaşma yaptık read -d ''. Bir NUL baytına kadar okur ve eğer bulursa doğru, false ise doğru döndürür. Bash, NUL baytlarını dizelerde işleyemez, bu nedenle readtrue olduğunda , NUL baytını yazdırırken elle ekleriz ve false döndürdüğünde, artık NUL baytı olmadığını biliyoruz ve bu, son veri parçası olmalı. .

Ortada NUL bulunan dosyalar üzerinde Bash 4.4 ile test edildi ve sıfıra, bir veya iki NUL ile ve ayrıca Debian'dan gelen wgetve curlikili dosyalara kadar bitti . 373 kB'lik wgetikili dosyayı indirmek yaklaşık 5.7 saniye sürdü. Yaklaşık 65 kB / s veya 512 kb / s'den biraz daha yüksek bir hız.

Buna karşılık, @ 131'in kedi çözeltisi, 0.1 s'den daha az bir sürede veya neredeyse yüz kat daha hızlı sonuç verir. Çok şaşırtıcı değil, gerçekten.

Bu aptalca aptalca, çünkü harici programları kullanmadan indirilen dosya ile yapabileceğimiz pek bir şey yok, çalıştırılabilir bile yapmıyoruz.


Eko bağımsız bir top mermisi değil mi? (: p)
131

1
@ 131, hayır! Bash vardır echove printfyerleşik olarak ( printfuygulamak için bir printf -v
yapıya

4

Bu pakete sahipseniz libwww-perl

Basitçe kullanabilirsiniz:

/usr/bin/GET

Diğer cevapların soru gereksinimine uymadığını düşünürsek (sadece bash), lynxPerl'in Lynx'e önceden monte edilmesinin daha muhtemel olduğu için bunun çözümden daha iyi olduğunu düşünüyorum .
Marcus

4

Yerel makinenizden SSH aracılığıyla bunun yerine yüklemeyi kullanın

Bir "minimal başsız * nix" kutusu, muhtemelen içine SSH koyacağınız anlamına gelir. Böylece SSH'yi de yüklemek için kullanabilirsiniz . Tabii ki başsız sunucunuzda bir betik içerisine bir indirme komutu eklemek istemeniz haricinde , indirmeye (yazılım paketlerinin vb.) Eşdeğerdir.

Bu cevapta gösterildiği gibi , uzaktaki başsız sunucunuza bir dosya yerleştirmek için yerel makinenizde aşağıdakileri uygularsınız:

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

Üçüncü bir makineden SSH ile daha hızlı yükleme

Yukarıdaki çözümün indirme işlemine kıyasla dezavantajı düşük aktarım hızıdır, çünkü yerel makinenizle olan bağlantı genellikle başsız sunucunuz ve diğer sunucular arasındaki bağlantıdan çok daha az bant genişliğine sahiptir.

Bunu çözmek için, elbette yukarıdaki komutu başka bir sunucuda uygun bant genişliğine sahip bir şekilde çalıştırabilirsiniz. Bunu daha rahat hale getirmek için (üçüncü makineye manuel giriş yapmaktan kaçının), yerel makinenizde çalıştırmanız gereken bir komut .

Güvenli olması için , önde gelen boşluk karakteri de dahil olmak üzere bu komutu kopyalayıp yapıştırın ' '. Sebep için aşağıdaki açıklamalara bakınız.

 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' \
"

açıklamalar:

  • Komut üçüncü makinenize gönderilecek intermediate-host, buradan bir dosya indirmeye wgetbaşlayacak ve target-hostSSH ile yüklemeye başlayacaktır . İndirme ve yükleme, sizin bant genişliğini kullanır ve intermediate-hostaynı anda olur (Bash eşdeğeri nedeniyle), bu nedenle ilerleme hızlı olacaktır.

  • Bunu kullanırken, iki sunucu girişini ( user@*-host), hedef ana bilgisayar şifresini ( yourpassword), indirme URL'sini ( http://example.com/…) ve hedef ana makinenizdeki ( /path/to/output-file.zip) çıkış yolunu uygun kendi değerleriyle değiştirmeniz gerekir.

  • İçin -T -e nonedosyaları aktarmanın kullanarak SSH seçenekleri, bkz bu ayrıntılı açıklamalar .

  • Bu komut, SSH'nin ortak anahtar kimlik doğrulama mekanizmasını kullanamayacağınız durumlar içindir - yine de bazı Avrupa Hosting sağlayıcıları, özellikle de Host Europe'da gerçekleşir . Süreci otomatikleştirmek için sshpass, komutta şifreyi sağlayabileceğimize güveniyoruz . Bu gerektirir sshpass(senin ara ana yüklü olması sudo apt-get install sshpassUbuntu altında).

  • sshpassGüvenli bir şekilde kullanmaya çalışıyoruz , ancak yine de SSH pubkey mekanizması kadar güvenli olmayacak (diyor man sshpass). Özellikle, SSH şifresini bir komut satırı argümanı olarak değil, hiçbir zaman diskte bulunmadığından emin olmak için bash işleminin yerine koyulmasıyla değiştirilen bir dosya üzerinden sağlarız. printfEmin bu kod parçası ayrı bir komut olarak açılır değildir yapım yerleşik bir bash olduğu pso şifreyi [ifşa edeceği şekilde çıkış kaynağı ]. Ben düşünüyorum bu kullanımı o sshpasssadece kadar güvenlidir sshpass -d<file-descriptor>tavsiye varyantı man sshpassbash böyle bir için şirket içinde eşler, çünkü /dev/fd/*zaten dosya tanımlayıcı. Ve bu geçici dosya kullanmadan [ kaynak]. Ama garanti yok, belki bir şeyi gözden kaçırdım.

  • Tekrar sshpasskullanımı güvenli hale getirmek için , komutun yerel makinenizdeki bash geçmişine kaydedilmesini önlememiz gerekir. Bunun için, tüm komut bu etkiye sahip olan bir boşluk karakteriyle hazırlanmıştır.

  • -o StrictHostKeyChecking=noBölümü, bu hedef ana bilgisayara bağlı asla durumunda başarısız gelen komutu engeller. (Normalde, SSH daha sonra kullanıcı girişinin bağlantı girişimini onaylamasını bekler. Yine de devam etmesini sağlarız.)

  • sshpassa sshveya scpkomutunu son argümanı olarak bekler . Bu yüzden wget -O - … | ssh …, burada açıklandığı gibi , tipik komutu bash borusu olmayan bir forma yeniden yazmak zorundayız .


3

@ Chris Kar tarifi dayalı. Bazı iyileştirmeler yaptım:

  • http şeması kontrolü (yalnızca http’i destekler)
  • http yanıt doğrulaması (yanıt durum satırı kontrolü ve başlığı ve gövdeyi '\ r \ n' satırına ayırın, 'Bağlantı: kapat' değil bazen doğru değildir)
  • 200 olmayan kodda başarısız oldu (internette dosya indirmek önemlidir)

İşte kod:

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>&-
}

Güzel geliştirmeler +1
Chris Snow

İşe yaradı, ama bir kaygı buldum, bu komut dosyalarını kullandığımda, tüm veriler okunduğunda birkaç saniye beklemeye devam etti, bu durum @Chris Snow cevabında olmadı, kimse bunu açıklayabilir mi?
zw963

Ve bu cevap, echo -en "GET ${PATH} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3, ${tag}belirtilmemiştir.
zw963

Bu cevabı tagdeğişkenlerle düzeltiyorum doğru ayarlanmış, şimdi iyi çalışıyor.
zw963

zsh, __wget google.com ile çalışmıyor, üzgünüm, sadece http / usr / bin / env: bash: böyle bir dosya veya dizin yok
vrkansagara
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.