bashのみを使用して(curl、wget、perlなどを使用しない)ファイルをダウンロードする方法


40

ファイルをダウンロードするためのコマンドラインユーティリティを持たない最小限のヘッドレス* nixがあります(例:curl、wgetなど)。私にはbashしかありません。

ファイルをダウンロードするにはどうすればよいですか?

理想的には、幅広い* nixで機能するソリューションが欲しいです。


どうgawk
ですか

gawkが利用可能かどうかは今は思い出せませんが、もしあればgawkベースのソリューションを見たいです:)
クリススノー

1
例は次のとおり
ニール

回答:


64

/dev/tcp擬似デバイスを有効にしてbash 2.04以降を使用している場合は、bash自体からファイルをダウンロードできます。

次のコードを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/

出典:Moreakiの答えは、cygwinコマンドラインを使用したパッケージのアップグレードとインストールですか?

更新: コメントで述べたように、上記で概説したアプローチは単純です。

  • これにより、readバックスラッシュと先頭の空白が破棄されます。
  • BashはNULバイトをうまく処理できないため、バイナリファイルが公開されます。
  • 引用符なしの$lineグロブ。

8
それで、あなたは自分の質問に答えたと同時に答えました。それはあなたが持っているおもしろいタイムマシンです;)
Meer Borg

11
@MeerBorg - -あなたが質問をするときには、チェックボックスのための外観は、「あなた自身の質問に答える」blog.stackoverflow.com/2011/07/...
クリス雪の

@eestartup-自分の答えに投票できるとは思わない。コードを説明できますか?未だに!しかし、cygwinでは機能します。
クリススノー

3
注:これは、Bashの一部の構成では機能しません。DebianはBashの配布からこの機能を設定していると思います。

1
Urgh、これは素晴らしいトリックですが、簡単に破損したダウンロードを引き起こす可能性があります。while readそのように、バックスラッシュと先頭の空白をゴミ箱に捨て、BashはNULバイトをうまく扱えないため、バイナリファイルがなくなります。そして、引用符で囲まれていない$lineグロブ...私は答えの中で言及したこれらのどれも。
イルッカチュ

19

lynxを使用します。

ほとんどのUnix / Linuxでかなり一般的です。

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

-dump:最初のファイルを標準出力にダンプして終了

man lynx

またはnetcat:

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

またはtelnet:

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

5
OPには「* nixにはファイルをダウンロードするためのコマンドラインユーティリティがない」ため、lynxはありません。
セラダ14

2
メモlynx -sourceはwgetに近い
スティーブンペニー14

ねえ、これは本当に遅いコメントですが、telnetコマンドの出力をどのようにファイルに保存しますか?「>」でリダイレクトすると、「Trying 93.184.216.34 ... Connected to www.example.com。」など、ファイルの内容とtelnet出力の両方が出力されます。私はtelnetしか使用できない状況にあり、最小限のフレームワークでchroot刑務所を作ろうとしています。
ピクセロマー

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

これはバイナリ転送ファイルを処理しません-nullバイトで失敗します。
ワイルドカード

@Wildcard、わかりません、バイナリファイル転送の例(ヌルバイトを含む)で編集しましたが、不足しているものを指摘できますか?
131

2
@Wildcard、へー、ええ、実際にファイルデータをで読み取るため、動作するはずcatです。それが不正行為(純粋にシェルではないため)なのか、それとも良い解決策(cat結局のところ標準ツールであるため)のかわかりません。しかし、@ 131では、他のソリューションよりも優れている理由についてのメモをここに追加することができます。
ilkkachu

@Wildcard、私は以下の答えとして純粋なbashソリューションも追加しました。そして、はい、不正行為かどうか、これは有効な解決策であり、
賛成の

7

Bashだけで他に」を厳密に取り上げると、外部ユーティリティ(標準ユーティリティでさえも)を呼び出さず、バイナリファイルでも機能する、以前の回答(@Chris@ 131)の1つの適応があります。

#!/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ます。

NULバイトはで処理しますread -d ''。NULバイトまで読み取り、見つかった場合はtrueを返し、見つからなかった場合はfalseを返します。Bashは文字列内のNULバイトを処理できないため、readtrueを返した場合、印刷時に手動でNULバイトを追加し、falseを返した場合、NULバイトがなくなったことを認識し、これが最後のデータになるはずです。 。

中間にNULがあり、0、1、または2つのNULで終わるファイルでBash 4.4でテストされ、Debianのwgetおよびcurlバイナリもあります。373 kBのwgetバイナリのダウンロードには約5.7秒かかりました。約65 kB / sまたは512 kb / sを少し超える速度。

それに比べて、@ 131のcat-solutionは0.1秒未満で終了し、ほぼ100倍高速です。それほど驚くことではありません、本当に。

これは明らかに愚かです。外部ユーティリティを使用しないと、ダウンロードしたファイルでできることはほとんどなく、実行可能にすることすらできないからです。


スタンドアロンの-non shell-バイナリをエコーし​​ませんか?(:p)
131

1
@ 131、いいえ!バッシュを有するechoprintf(それが組み込み必要組み込み関数としてprintf実装するがprintf -v
ilkkachu

4

このパッケージをお持ちの場合libwww-perl

次を使用できます。

/usr/bin/GET

他の回答は質問の要件を尊重しないことを考慮して(bashのみ)、これは実際にはlynxソリューションよりも優れていると思います。
マーカス

4

代わりに、ローカルマシンからSSH経由でアップロードを使用します

「最小のヘッドレス* nix」ボックスは、おそらくSSHで接続することを意味します。したがって、SSHを使用してアップロードすることもできます。もちろん、ダウンロードコマンドをヘッドレスサーバーのスクリプトに含める場合を除き、機能的には(ソフトウェアパッケージなどの)ダウンロードと同等です。

この回答に示されいるように、ローカルマシンで次のコマンドを実行して、リモートヘッドレスサーバーにファイルを配置します。

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

3番目のマシンからのSSH経由の高速アップロード

ダウンロードと比較した上記のソリューションの欠点は、転送速度が遅いことです。ローカルマシンとの接続は、通常、ヘッドレスサーバーと他のサーバー間の接続よりも帯域幅がはるかに少ないためです。

それを解決するために、まともな帯域幅を持つ別のサーバーで上記のコマンドを実行できます。これをより快適にするために(3番目のマシンでの手動ログインを回避するため)、ローカルマシンで実行するコマンドを次に示します

安全にするために、先頭のスペース文字を含む コマンドコピーして貼り付け' 'ます。理由については、以下の説明を参照してください。

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

説明:

  • コマンドは3番目のマシンにsshをintermediate-host実行し、そこからファイルのダウンロードwgetを開始し、target-hostSSH 経由でファイルのアップロードを開始します。ダウンロードとアップロードは帯域幅を使用intermediate-hostし、同時に発生します(Bashパイプに相当するため)ので、進行は速くなります。

  • これを使用する場合、2つのサーバーログイン(user@*-host)、ターゲットホストパスワード(yourpassword)、ダウンロードURL(http://example.com/…)、ターゲットホスト上の出力パス(/path/to/output-file.zip)を適切な独自の値に置き換える必要があります。

  • -T -e noneファイルを転送するために使用する場合のSSHオプションについては、これらの詳細な説明を参照してください。

  • このコマンドは、SSHの公開キー認証メカニズムを使用できない場合に使用します。これは、いくつかの共有ホスティングプロバイダー、特にHost Europeで発生します。プロセスを自動化するためsshpassに、コマンドでパスワードを提供できることに依存しています。sshpass中間ホスト(sudo apt-get install sshpassUbuntuの下)にインストールする必要があります。

  • 私たちsshpassは安全な方法で使用しようとしますが、SSH pubkeyメカニズム(たとえばman sshpass)ほど安全ではありません。特に、コマンドライン引数としてではなく、ファイルを介してSSHパスワードを提供します。ファイルは、bashプロセスの置換によって置き換えられ、ディスク上に存在しないようにします。これprintfはbashビルトインであり、コードのこの部分がpsパスワード[ source ]を公開するため、出力に別のコマンドとして表示されないようにします。私が考えるのこの使用はことをsshpass同じように安全なようであるsshpass -d<file-descriptor>に推奨バリアントman sshpassbashはこのように内部的にマッピングしているため、/dev/fd/*とにかくファイルディスクリプタ。そして、一時ファイルを使用せずに[ ソース]。しかし、保証はありません。何かを見落としているかもしれません。

  • 再び作るためにsshpass使用状況の安全を、私たちはあなたのローカルマシン上のbash履歴に記録されてからコマンドを防ぐために必要です。そのため、コマンド全体の先頭にスペース文字が1つ追加されますが、この効果があります。

  • この-o StrictHostKeyChecking=no部分は、ターゲットホストに接続したことがない場合にコマンドが失敗するのを防ぎます。(通常、SSHはユーザーの入力を待って接続試行を確認します。それでも続行します。)

  • sshpass最後の引数としてsshor scpコマンドが必要です。したがってwget -O - … | ssh …ここで説明するように、典型的なコマンドをbashパイプのないフォームに書き換える必要があります


3

@Chris Snowレシピに基づいています。私はいくつかの改善を行いました:

  • httpスキームチェック(httpのみをサポート)
  • http応答の検証(応答ステータス行のチェック、およびヘッダーと本文を 'Connection:close'ではなく '\ 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 Snowの回答では発生しません、誰もこれを説明できますか?
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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.