web-dev-qa-db-ja.com

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

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

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

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

45
Chris Snow

/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コマンドラインを使用したパッケージのアップグレードとインストール?

Update:コメントで述べたように、上記のアプローチは単純化されています:

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

Lynxを使用します。

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

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

-dump:最初のファイルをstdoutにダンプして終了します

man lynx

またはnetcat:

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

またはtelnet:

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

クリススノーの回答を基にしています。これはバイナリ転送ファイルも処理できます。

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
11
131

just Bash andnothingelse」を厳密に取り上げると、これは以前の回答(- @ Chris's@ 131's )これは、外部ユーティリティ(標準のユーティリティでさえも)を呼び出さず、バイナリファイルでも機能します。

#!/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バイトを処理できないため、readがtrueで返される場合は、印刷時にNULバイトを手動で追加し、falseが返される場合は、NULバイトがないことを確認します。これにより、最後のデータになります。

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

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

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

8
ilkkachu

代わりにローカルマシンからSSH経由でアップロードを使用してください

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

this answer に示すように、 local マシンで次のコマンドを実行して、リモートヘッドレスサーバーにファイルを配置します。

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番目のマシンintermediate-HostにSSHで接続し、wgetを介してそこにファイルのダウンロードを開始し、SSHを介してtarget-Hostにアップロードを開始します。ダウンロードとアップロードはintermediate-Hostの帯域幅を使用し、同時に発生するため(Bashパイプの同等機能により)、進行が速くなります。

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

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

  • このコマンドは、SSHの公開鍵認証メカニズムを使用できない場合を対象としています。一部の共有ホスティングプロバイダー 特にHost Europe でも発生します。プロセスを自動化するために、sshpassを使用してコマンドにパスワードを入力できるようにしています。中間ホスト(UbuntuではSudo apt-get install sshpass)にsshpassをインストールする必要があります。

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

  • ここでも、sshpassの使用を安全にするために、コマンドがローカルマシンのbash履歴に記録されないようにする必要があります。そのため、コマンド全体に1つのスペース文字が付加され、これがこの効果をもたらします。

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

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

4
tanius

このパッケージlibwww-Perlがある場合

あなたは単に使うことができます:

/usr/bin/GET
4
stackexchanger

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

  • 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>&-
}
3
Yecheng Fu