Taking the "just Bash and nothing else" strictly, here's one adaptation of earlier answers (@Chris's, @131's) that does not call any external utilities (not even standard ones) but also works with binary files:
#!/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>&-
}
Use with download http://path/to/file > file
.
We deal with NUL bytes with read -d ''
. It reads until a NUL byte, and returns true if it found one, false if it didn't. Bash can't handle NUL bytes in strings, so when read
returns with true, we add the NUL byte manually when printing, and when it returns false, we know there are no NUL bytes any more, and this should be the last piece of data.
Tested with Bash 4.4 on files with NULs in the middle, and ending in zero, one or two NULs, and also with the wget
and curl
binaries from Debian. The 373 kB wget
binary took about 5.7 seconds to download. A speed of about 65 kB/s or a bit more than 512 kb/s.
In comparison, @131's cat-solution finishes in less than 0.1 s, or almost a hundred times faster. Not very surprising, really.
This is obviously silly, since without using external utilities, there's not much we can do with the downloaded file, not even make it executable.
gawk