を持たないファイルをダウンロードするためのコマンドラインユーティリティ(curl、wgetなどなし)を備えた最小限のヘッドレス* nixがあります。私はbashしか持っていません。
ファイルをダウンロードするにはどうすればよいですか?
理想的には、幅広い* nixで機能するソリューションが欲しいです。
/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
は、バックスラッシュと先頭の空白を破棄します。$line
はグロブします。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
クリススノーの回答を基にしています。これはバイナリ転送ファイルも処理できます。
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>&-
}
あなたはこのようなバイナリファイルをテストすることができます
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
「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倍の速さで終了します。それほど驚くべきことではありません。
これは明らかにばかげています。外部ユーティリティを使用しないと、ダウンロードしたファイルで実行できることはほとんどなく、実行可能にすることすらできません。
代わりにローカルマシンから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 を使用すると、sshpass
はsshpass -d<file-descriptor>
で推奨されるman sshpass
バリアントと同じくらい安全です。とにかく/dev/fd/*
ファイル記述子。そして、一時ファイルを使用せずに[ source ]。しかし、保証はありません。おそらく私は何かを見落としました。
ここでも、sshpass
の使用を安全にするために、コマンドがローカルマシンのbash履歴に記録されないようにする必要があります。そのため、コマンド全体に1つのスペース文字が付加され、これがこの効果をもたらします。
-o StrictHostKeyChecking=no
部分は、ターゲットホストに接続されていない場合にコマンドが失敗するのを防ぎます。 (通常、SSHはユーザー入力を待って接続試行を確認します。とにかく続行します。)
sshpass
は、最後の引数としてssh
またはscp
コマンドを想定しています。したがって、説明されている here のように、典型的なwget -O - … | ssh …
コマンドをbashパイプなしのフォームに書き直す必要があります。
このパッケージlibwww-Perlがある場合
あなたは単に使うことができます:
/usr/bin/GET
@Chris Snowのレシピに基づいています。私はいくつかの改善を行いました:
ここにコードがあります:
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>&-
}