我有一个最小的无头* nix,它没有任何命令行实用程序来下载文件(例如,curl,wget等)。我只有bash。
如何下载文件?
理想情况下,我想要一个适用于广泛* nix的解决方案。
我有一个最小的无头* nix,它没有任何命令行实用程序来下载文件(例如,curl,wget等)。我只有bash。
如何下载文件?
理想情况下,我想要一个适用于广泛* nix的解决方案。
Answers:
如果您具有/dev/tcp
启用了伪设备的bash 2.04或更高版本,则可以从bash本身下载文件。
将以下代码直接粘贴到bash shell中(您无需将代码保存到文件中即可执行):
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
意志象垃圾一样清除反斜线和前导空格。$line
会引起混乱。while read
像这样的话,就浪费了反斜杠和领先的空格,而Bash不能很好地处理NUL字节,因此二进制文件不可用。且未引用的单词$line
会出现问题。答案中没有提到任何这些。
使用山猫。
对于大多数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
lynx -source
更接近wget
改编自Chris Snow答案这也可以处理二进制传输文件
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
cat
。我不确定这是作弊(因为它不是纯粹的外壳)还是好的解决方案(cat
毕竟,因为它是标准工具)。但是@ 131,您可能要添加一条注释,说明为什么它比此处的其他解决方案更好。
严格地讲“ 仅Bash,别无其他 ”,这是对较早答案(@ 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字节了,这应该是最后一个数据。
使用Bash 4.4在中间有NUL,结尾为零,一个或两个NUL以及Debian中的wget
和curl
二进制文件的文件上进行了测试。373 kB wget
二进制文件的下载时间约为5.7秒。速度约为65 kB / s或略大于512 kb / s。
相比之下,@ 131的猫解决方案可以在不到0.1秒的时间内完成,或者快将近一百倍。确实不很奇怪。
这显然很愚蠢,因为如果不使用外部实用程序,我们对下载的文件将无能为力,甚至无法使其可执行。
echo
和printf
as内置printf
printf -v
而是通过本地计算机上的SSH使用上传
“最小无头* nix”框表示您可能通过SSH进入了该框。因此,您也可以使用SSH 上传到它。当然,在功能上等价于(软件包等的)下载,除了当您希望将下载命令包括在无头服务器上的脚本中时。
如该答案所示,您将在本地计算机上执行以下操作以将文件放置在远程无头服务器上:
wget -O - http://example.com/file.zip | ssh user@host 'cat >/path/to/file.zip'
通过SSH从第三台计算机更快地上传
与下载相比,上述解决方案的缺点是传输速度较低,因为与本地计算机的连接通常比无头服务器与其他服务器之间的连接具有更少的带宽。
为了解决这个问题,您当然可以在带宽合适的另一台服务器上执行上述命令。为了使操作更舒适(避免在第三台计算机上手动登录),以下是在本地计算机上执行的命令。
为了安全起见,请复制并粘贴包含前导空格字符的 命令' '
。原因请参见以下说明。
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' \
"
说明:
该命令将SSH到您的第三台计算机intermediate-host
,开始通过下载文件到wget
,然后开始target-host
通过SSH 将其上传到。下载和上传使用您的带宽intermediate-host
并且同时发生(由于Bash管道等效),因此进度会很快。
使用此功能时,必须用适当的值替换两个服务器登录名(user@*-host
),目标主机密码(yourpassword
),下载URL(http://example.com/…
)和目标主机上的输出路径(/path/to/output-file.zip
)。
有关使用-T -e none
SSH传输文件时的SSH选项,请参见这些详细说明。
该命令适用于无法使用SSH的公钥身份验证机制的情况-在某些共享托管服务提供商(尤其是Host Europe)中仍然会发生此命令。为了仍然使过程自动化,我们依靠sshpass
能够在命令中提供密码。它需要sshpass
安装在您的中间主机上(sudo apt-get install sshpass
在Ubuntu下)。
我们尝试以sshpass
安全的方式使用,但是它仍然不如SSH pubkey机制安全(例如man sshpass
)。特别是,我们不是通过命令行参数而是通过文件来提供SSH密码,该文件被bash进程替换所替换,以确保它在磁盘上不存在。的printf
是内置在bash,确保这部分代码不弹出作为一个单独的命令ps
输出,因为这将暴露出密码[ 源。我认为的使用与推荐sshpass
的sshpass -d<file-descriptor>
变体一样安全man sshpass
,因为bash 始终会在内部将其映射到这样的/dev/fd/*
文件描述符。而且,这无需使用临时文件[ 来源]。但是没有保证,也许我忽略了一些东西。
同样,为了确保sshpass
使用安全,我们需要防止将命令记录到本地计算机上的bash历史记录中。为此,整个命令以一个空格字符开头,具有此效果。
该-o StrictHostKeyChecking=no
部分可防止命令在从未连接到目标主机的情况下失败。(通常,SSH随后将等待用户输入以确认连接尝试。无论如何,我们将使其继续进行。)
sshpass
期望ssh
or scp
命令作为其最后一个参数。因此,我们不得不重新改写典型的wget -O - … | ssh …
命令,不发一庆典管形式,如解释在这里。
基于@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>&-
}
echo -en "GET ${PATH} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3
, ${tag}
未指定。
tag
变量设置了正确的答案,现在可以正常工作了。
gawk