Wie lade ich eine Datei mit nur Bash und nichts anderem herunter (ohne Curl, Wget, Perl usw.)?

40

Ich habe ein minimales kopflos * nichts , das nicht hat keine Befehlszeilen - Dienstprogramme für das Herunterladen von Dateien (zB keine Locke, wget, etc). Ich habe nur Bash.

Wie kann ich eine Datei herunterladen?

Im Idealfall hätte ich gerne eine Lösung, die in einer Vielzahl von * nix funktioniert.

Chris Snow
quelle
wie wäre esgawk
Neil McGuigan
Ich kann mich jetzt nicht erinnern, ob gawk verfügbar war, obwohl ich gerne eine gawk-basierte Lösung sehen würde, wenn Sie eine haben :)
Chris Snow
1
Hier ist ein Beispiel: gnu.org/software/gawk/manual/gawkinet/gawkinet.html#Web-page
Neil McGuigan

Antworten:

64

Wenn Sie Bash 2.04 oder höher mit /dev/tcpaktiviertem Pseudogerät haben, können Sie eine Datei von Bash selbst herunterladen.

Fügen Sie den folgenden Code direkt in eine Bash-Shell ein (Sie müssen den Code nicht in einer Datei speichern, um ihn auszuführen):

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

Dann können Sie es wie folgt aus der Shell ausführen:

__wget http://example.iana.org/

Quelle: Moreakis Antwort: Aktualisieren und Installieren von Paketen über die Cygwin-Befehlszeile?

Update: Wie im Kommentar erwähnt, ist der oben beschriebene Ansatz einfach:

  • Der readWille verwirft Backslashes und führende Leerzeichen.
  • Bash kann nicht sehr gut mit NUL-Bytes umgehen, so dass Binärdateien herauskommen.
  • nicht zitierter $lineWille glob.
Chris Snow
quelle
8
Sie haben also Ihre eigene Frage zur gleichen Zeit beantwortet, als Sie sie gestellt haben. Das ist eine interessante Zeitmaschine, die Sie haben;)
Meer Borg
11
@ MeerBorg - Wenn Sie eine Frage stellen, aktivieren Sie das Kontrollkästchen "Beantworten Sie Ihre eigene Frage" - blog.stackoverflow.com/2011/07/…
Chris Snow
@eestartup - Ich glaube nicht, dass Sie für Ihre eigene Antwort stimmen können. Kann ich den Code erklären? Noch nicht! Aber es funktioniert auf Cygwin.
Chris Snow
3
Nur eine Anmerkung: Dies funktioniert bei einigen Konfigurationen von Bash nicht. Ich glaube, Debian konfiguriert diese Funktion aus der Distribution von Bash heraus.
1
Urgh, obwohl dies ein netter Trick ist, kann es zu leicht korrupte Downloads verursachen. while readAuf diese Weise werden Backslashes und führende Whitespaces in den Papierkorb verschoben, und Bash kann mit NUL-Bytes nicht sehr gut umgehen, sodass Binärdateien ausgegeben werden. Und nicht zitiert $linewird glob ... Nichts davon sehe ich in der Antwort erwähnt.
ilkkachu
19

Verwenden Sie Luchs.

Es ist für die meisten Unix / Linux-Betriebssysteme ziemlich verbreitet.

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

-dump: speichert die erste Datei in stdout und beendet sie

man lynx

Oder netcat:

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

Oder telnet:

(echo 'GET /'; echo ""; sleep 1; ) | telnet www.google.com 80
Holzstapel
quelle
5
Das OP hat "* nix, das keine Kommandozeilenprogramme zum Herunterladen von Dateien hat", also keinen Luchs.
Celada
2
Hinweis lynx -sourceist näher zu wget
Steven Penny
Hey, das ist also ein sehr später Kommentar, aber wie speichert man die Ausgabe des Telnet-Befehls in einer Datei? Durch Umleiten mit ">" werden sowohl der Inhalt der Datei als auch die Telnet-Ausgabe ausgegeben, z. B. "93.184.216.34 wird versucht ... Verbunden mit www.example.com.". Ich bin in einer Situation, in der ich nur Telnet verwenden kann. Ich versuche, ein Chroot-Gefängnis mit den geringstmöglichen Rahmenbedingungen zu schaffen.
Pixelomer
10

Adaptiert von Chris Snow answer Dies kann auch binäre Übertragungsdateien verarbeiten

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>&-
}
  • Ich breche && Katze, um nicht mehr zu lesen
  • Ich benutze http 1.0, so gibt es keine Notwendigkeit zu warten / senden Sie eine Verbindung: schließen

Sie können solche Binärdateien testen

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
131
quelle
Hiermit werden keine binären Übertragungsdateien verarbeitet. Bei Null-Bytes schlägt dies fehl.
Wildcard
@Wildcard, ich verstehe nicht, ich habe mit einem Beispiel für binäre Dateiübertragung (mit null Bytes) bearbeitet. Können Sie mir sagen, was mir fehlt?
131.
2
@Wildcard, heheh, ja das sieht so aus, als sollte es funktionieren, da es die eigentlichen Dateidaten mit liest cat. Ich bin mir nicht sicher, ob das Schummeln ist (da es nicht nur die Shell ist) oder eine gute Lösung (schließlich catist es ein Standardwerkzeug). Bei @ 131 möchten Sie möglicherweise einen Hinweis hinzufügen, warum dies besser funktioniert als die anderen Lösungen hier.
Ilkkachu
@Wildcard, ich habe auch die reine Bash-Lösung als Antwort unten hinzugefügt. Und ja, betrügen oder nicht, dies ist eine gültige Lösung und eine positive
Bewertung
7

Unter dem „ nur Bash und nichts anderes “ streng, hier ist eine Anpassung der früherer Antworten ( @ Chris , @ 131 ist ) , die keine externen Versorgungs nicht nennen (auch nicht Standard ist) , sondern arbeitet auch mit binären Dateien:

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

Verwenden Sie mit download http://path/to/file > file.

Wir beschäftigen uns mit NUL Bytes mit read -d ''. Es liest bis zu einem NUL-Byte und gibt true zurück, wenn es eines gefunden hat, false, wenn dies nicht der Fall ist. Bash kann keine NUL-Bytes in Strings verarbeiten. Wenn also readtrue zurückgegeben wird, wird das NUL-Byte beim Drucken manuell hinzugefügt. Wenn false zurückgegeben wird, wissen wir, dass keine NUL-Bytes mehr vorhanden sind. Dies sollte das letzte Datenelement sein .

Getestet mit Bash 4.4 für Dateien mit NULs in der Mitte und endend mit null, einer oder zwei NULs sowie mit den Binärdateien wgetund curlvon Debian. Das wgetHerunterladen der 373-kB- Binärdatei dauerte etwa 5,7 Sekunden. Eine Geschwindigkeit von ca. 65 kB / s oder etwas mehr als 512 kB / s.

Im Vergleich dazu ist die cat-solution von @ 131 in weniger als 0,1 s oder fast hundertmal schneller fertig. Eigentlich nicht sehr überraschend.

Das ist natürlich albern, da wir ohne externe Dienstprogramme nicht viel mit der heruntergeladenen Datei anfangen können, nicht einmal, um sie ausführbar zu machen.

ilkkachu
quelle
Ist echo nicht eine eigenständige Binärdatei? (: p)
131
1
@ 131, nein! Bash hat echound printfals Builtins (es braucht ein Builtin printfzu implementieren printf -v)
ilkkachu
4

Wenn Sie dieses Paket libwww-perl haben

Sie können einfach verwenden:

/usr/bin/GET
stapelaustauscher
quelle
In Anbetracht der Tatsache, dass andere Antworten die Fragenanforderungen nicht erfüllen (nur Bash), denke ich, dass dies tatsächlich besser ist als die lynxLösung, da Perl mit größerer Wahrscheinlichkeit vorinstalliert ist als Lynx.
Marcus
4

Verwenden Sie stattdessen das Hochladen über SSH von Ihrem lokalen Computer

Eine "minimal headless * nix" Box bedeutet, dass Sie wahrscheinlich SSH drin haben. Sie können SSH also auch zum Hochladen verwenden . Dies entspricht in der Funktion dem Herunterladen (von Softwarepaketen usw.), es sei denn, Sie möchten, dass ein Download-Befehl in ein Skript auf Ihrem Headless-Server eingefügt wird.

Wie in dieser Antwort gezeigt , würden Sie Folgendes auf Ihrem lokalen Computer ausführen , um eine Datei auf Ihrem Remote-Headless-Server abzulegen:

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

Schnelleres Hochladen über SSH von einem dritten Computer

Der Nachteil der oben genannten Lösung gegenüber dem Herunterladen ist die geringere Übertragungsgeschwindigkeit, da die Verbindung mit Ihrem lokalen Computer in der Regel eine viel geringere Bandbreite aufweist als die Verbindung zwischen Ihrem Headless-Server und anderen Servern.

Um dies zu beheben, können Sie den obigen Befehl natürlich auf einem anderen Server mit angemessener Bandbreite ausführen. Um dies zu vereinfachen (Vermeidung einer manuellen Anmeldung auf dem dritten Computer), finden Sie hier einen Befehl, den Sie auf Ihrem lokalen Computer ausführen müssen .

Um sicher zu gehen, kopieren Sie diesen Befehl mit dem führenden Leerzeichen und fügen ihn ein ' '. Siehe die folgenden Erklärungen für den Grund.

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

Erklärungen:

  • Der Befehl sendet ssh an Ihren dritten Computer intermediate-host, lädt dort über eine Datei herunter wgetund lädt sie target-hostüber SSH hoch. Das Herunterladen und Hochladen nutzt die Bandbreite von intermediate-hostund erfolgt gleichzeitig (aufgrund von Bash-Pipe-Entsprechungen), sodass die Fortschritte schnell sind.

  • In diesem Fall müssen Sie die beiden Serveranmeldungen ( user@*-host), das Zielhostkennwort ( yourpassword), die Download-URL ( http://example.com/…) und den Ausgabepfad auf Ihrem Zielhost ( /path/to/output-file.zip) durch entsprechende eigene Werte ersetzen .

  • Informationen zu den -T -e noneSSH-Optionen beim Übertragen von Dateien finden Sie in diesen ausführlichen Erläuterungen .

  • Dieser Befehl ist für Fälle gedacht, in denen Sie den Authentifizierungsmechanismus für öffentliche Schlüssel von SSH nicht verwenden können. Dies ist bei einigen gemeinsam genutzten Hosting-Anbietern, insbesondere Host Europe, immer noch der Fall . Um den Prozess dennoch zu automatisieren, müssen wir sshpassdas Kennwort im Befehl angeben können. Es muss sshpassauf Ihrem Zwischenhost ( sudo apt-get install sshpassunter Ubuntu) installiert sein .

  • Wir versuchen, sshpassauf sichere Weise zu verwenden, aber es wird immer noch nicht so sicher sein wie der SSH-Pubkey-Mechanismus (sagt man sshpass). Insbesondere geben wir das SSH-Kennwort nicht als Befehlszeilenargument, sondern über eine Datei an, die durch die Ersetzung des Bash-Prozesses ersetzt wird, um sicherzustellen, dass es niemals auf der Festplatte vorhanden ist. Das printfist eine eingebaute Bash, die sicherstellt, dass dieser Teil des Codes nicht als separater Befehl in der psAusgabe erscheint, da dies das Passwort [ source ] enthüllen würde . Ich denke, dass diese Verwendung von sshpassgenauso sicher ist wie die in sshpass -d<file-descriptor>empfohlene Variante man sshpass, da bash sie /dev/fd/*sowieso intern einem solchen Dateideskriptor zuordnet. Und das ohne Verwendung einer temporären Datei [ Quelle]. Aber keine Garantie, vielleicht habe ich etwas übersehen.

  • Um die sshpassVerwendung sicherer zu machen , müssen wir verhindern, dass der Befehl im Bash-Verlauf auf Ihrem lokalen Computer aufgezeichnet wird. Dafür wird dem gesamten Befehl ein Leerzeichen vorangestellt, was diesen Effekt hat.

  • Das -o StrictHostKeyChecking=noTeil verhindert, dass der Befehl fehlschlägt, falls keine Verbindung zum Zielhost hergestellt wird. (Normalerweise wartet SSH dann auf Benutzereingaben, um den Verbindungsversuch zu bestätigen. Wir veranlassen es trotzdem.)

  • sshpasserwartet einen sshoder scpBefehl als letztes Argument. Wir müssen also den typischen wget -O - … | ssh …Befehl in ein Formular ohne Bash-Pipe umschreiben , wie hier erläutert .

Tanius
quelle
3

Basierend auf @ Chris Snow Rezept. Ich habe einige Verbesserungen vorgenommen:

  • http-Schemaprüfung (unterstützt nur http)
  • http-Antwortvalidierung (Überprüfung der Antwortstatuszeile und Aufteilung von Header und Text nach '\ r \ n'-Zeile, nicht nach' Verbindung: Schließen ', was manchmal nicht zutrifft)
  • Fehler bei Code, der nicht 200 ist (es ist wichtig, Dateien im Internet herunterzuladen)

Hier ist Code:

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>&-
}
Yecheng Fu
quelle
Schöne Verbesserungen +1
Chris Snow
Es hat funktioniert, aber ich habe ein Problem festgestellt, wenn ich diese Skripte verwende. Es dauert einige Sekunden, bis alle Daten gelesen wurden. Dieser Fall tritt nicht in der @ Chris Snow-Antwort auf. Kann jemand dies erklären?
zw963
Und in dieser Antwort echo -en "GET ${PATH} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3, ${tag}ist nicht angegeben.
zw963
Ich bearbeite diese Antwort mit der tagrichtigen Variableneinstellung, es funktioniert nun gut.
zw963
funktioniert nicht mit zsh, __wget google.com sorry, unterstütze nur http / usr / bin / env: bash: Keine solche Datei oder Verzeichnis
vrkansagara