Mam minimalny * nix bezgłowy, który nie ma żadnych narzędzi wiersza poleceń do pobierania plików (np. Bez curl, wget itp.). Mam tylko uderzenie.
Jak mogę pobrać plik?
Idealnie chciałbym mieć rozwiązanie, które działałoby w szerokim zakresie * nix.
Mam minimalny * nix bezgłowy, który nie ma żadnych narzędzi wiersza poleceń do pobierania plików (np. Bez curl, wget itp.). Mam tylko uderzenie.
Jak mogę pobrać plik?
Idealnie chciałbym mieć rozwiązanie, które działałoby w szerokim zakresie * nix.
Odpowiedzi:
Jeśli masz wersję bash 2.04 lub nowszą z /dev/tcp
włączonym pseudo-urządzeniem, możesz pobrać plik z samej wersji bash.
Wklej następujący kod bezpośrednio do powłoki bash (nie musisz zapisywać kodu w pliku do wykonania):
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>&-
}
Następnie możesz wykonać go z poziomu powłoki w następujący sposób:
__wget http://example.iana.org/
Źródło: odpowiedź Moreaki dotycząca aktualizacji i instalacji pakietów za pomocą wiersza poleceń cygwin?
Aktualizacja: jak wspomniano w komentarzu, podejście opisane powyżej jest uproszczone:
read
wola trashes backslashy i prowadząc spacje.$line
będzie glob.while read
tak, że kasuje ukośniki odwrotne i wiodące białe znaki, a Bash nie radzi sobie bardzo dobrze z bajtami NUL, więc pliki binarne są usuwane. I nie cytowany $line
będzie glob ... Nic z tego nie widzę wspomniane w odpowiedzi.
Użyj rysia.
Jest to dość powszechne w większości systemów Unix / Linux.
lynx -dump http://www.google.com
-dump: zrzuć pierwszy plik na standardowe wyjście i wyjdź
man lynx
Lub netcat:
/usr/bin/printf 'GET / \n' | nc www.google.com 80
Lub telnet:
(echo 'GET /'; echo ""; sleep 1; ) | telnet www.google.com 80
lynx -source
jest bliższa wget
Na podstawie odpowiedzi Chrisa Snowa Może to również obsługiwać pliki binarne
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>&-
}
Możesz testować takie pliki binarne
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
. Nie jestem pewien, czy to oszustwo (ponieważ nie jest to wyłącznie powłoka), czy fajne rozwiązanie (ponieważ cat
w końcu jest to standardowe narzędzie). Ale @ 131, możesz dodać notatkę o tym, dlaczego działa lepiej niż inne rozwiązania tutaj.
Przyjmując ściśle „ tylko Bash i nic więcej ”, oto jedna adaptacja wcześniejszych odpowiedzi ( @ Chris's , @ 131's ), która nie wywołuje żadnych zewnętrznych narzędzi (nawet standardowych), ale działa również z plikami binarnymi:
#!/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>&-
}
Użyj z download http://path/to/file > file
.
Mamy do czynienia z bajtami NUL read -d ''
. Czyta do bajtu NUL i zwraca true, jeśli go znalazł, a false, jeśli nie. Bash nie może obsłużyć bajtów NUL w ciągach, więc gdy read
zwraca true, dodajemy bajt NUL ręcznie podczas drukowania, a gdy zwraca false, wiemy, że nie ma już bajtów NUL i powinien to być ostatni kawałek danych .
Testowane z Bash 4.4 na plikach z NUL w środku, a kończące się na zero, jeden lub dwa NUL, a także z wget
i curl
binaria z Debiana. wget
Pobranie pliku binarnego o wielkości 373 kB zajęło około 5,7 sekundy. Prędkość około 65 kB / s lub nieco więcej niż 512 kb / s.
Dla porównania, rozwiązanie dla kota @ 131 kończy się w mniej niż 0,1 s, czyli prawie sto razy szybciej. Naprawdę niezbyt zaskakujące.
Jest to oczywiście głupie, ponieważ bez korzystania z zewnętrznych narzędzi nie możemy wiele zrobić z pobranym plikiem, nawet nie można go uruchomić.
echo
i printf
jako wbudowane (potrzebuje wbudowanego printf
do wdrożenia printf -v
)
Jeśli masz ten pakiet libwww-perl
Możesz po prostu użyć:
/usr/bin/GET
lynx
rozwiązanie, ponieważ Perl jest z pewnością bardziej preinstalowany niż Lynx.
Zamiast tego użyj przesyłania przez SSH z lokalnego komputera
„Minimalna liczba bezgłowych * nix” oznacza, że prawdopodobnie używasz SSH. Możesz więc użyć SSH, aby przesłać do niego. Co jest funkcjonalnie równoważne pobieraniu (pakietów oprogramowania itp.), Z wyjątkiem sytuacji, gdy chcesz, aby polecenie pobierania zawierało się w skrypcie na twoim serwerze bezgłowym.
Jak pokazano w tej odpowiedzi , należy wykonać następujące czynności na komputerze lokalnym , aby umieścić plik na zdalnym serwerze bezgłowym:
wget -O - http://example.com/file.zip | ssh user@host 'cat >/path/to/file.zip'
Szybsze przesyłanie przez SSH z trzeciego komputera
Wadą powyższego rozwiązania w porównaniu do pobierania jest niższa prędkość transferu, ponieważ połączenie z lokalną maszyną ma zwykle znacznie mniejszą przepustowość niż połączenie między serwerem bezgłowym a innymi serwerami.
Aby rozwiązać ten problem, możesz oczywiście wykonać powyższe polecenie na innym serwerze z przyzwoitą przepustowością. Aby uczynić to wygodniejszym (unikając ręcznego logowania na trzecim komputerze), oto polecenie, które należy wykonać na komputerze lokalnym .
Aby zabezpieczyć się, skopiuj i wklej to polecenie, w tym wiodący znak spacji ' '
. Powód wyjaśniono poniżej.
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' \
"
Objaśnienia:
Polecenie prześle ssh na twój trzeci komputer intermediate-host
, rozpocznie pobieranie pliku do niego przez wget
i rozpocznie przesyłanie go na target-host
SSH. Pobieranie i przesyłanie wykorzystuj przepustowość twojego intermediate-host
i dzieje się w tym samym czasie (ze względu na odpowiedniki rur Bash), więc postęp będzie szybki.
Korzystając z tego, musisz zastąpić dwa loginy serwera ( user@*-host
), hasło hosta docelowego ( yourpassword
), adres URL pobierania ( http://example.com/…
) i ścieżkę wyjściową na hoście docelowym ( /path/to/output-file.zip
) odpowiednimi własnymi wartościami.
Aby -T -e none
zapoznać się z opcjami SSH podczas przesyłania plików, zobacz te szczegółowe wyjaśnienia .
To polecenie jest przeznaczone dla przypadków, w których nie można użyć mechanizmu uwierzytelniania klucza publicznego SSH - nadal występuje w przypadku niektórych dostawców hostingu współdzielonego, w szczególności Host Europe . Aby nadal automatyzować proces, polegamy na tym, że możemy sshpass
podać hasło w poleceniu. Wymaga sshpass
to zainstalowania na twoim hoście pośrednim ( sudo apt-get install sshpass
pod Ubuntu).
Staramy się używać sshpass
w bezpieczny sposób, ale nadal nie będzie tak bezpieczny, jak mechanizm klucza publicznego SSH (mówi man sshpass
). W szczególności podajemy hasło SSH nie jako argument wiersza poleceń, ale poprzez plik, który jest zastępowany przez podstawienie procesu bash, aby upewnić się, że nigdy nie istnieje na dysku. Jest printf
to wbudowany bash, upewniający się, że ta część kodu nie wyskakuje jako osobne polecenie ps
wyjściowe, ponieważ odsłoniłoby to hasło [ źródło ]. Myślę , że takie użycie sshpass
jest tak samo bezpieczne, jak sshpass -d<file-descriptor>
zalecany wariant man sshpass
, ponieważ bash i tak mapuje go wewnętrznie na taki /dev/fd/*
deskryptor pliku. I to bez użycia pliku tymczasowego [ źródło]. Ale żadnych gwarancji, może coś przeoczyłem.
Ponownie, aby sshpass
korzystanie było bezpieczne, musimy zapobiec zapisywaniu polecenia w historii bash na twoim komputerze lokalnym. W tym celu całe polecenie jest poprzedzone jednym znakiem spacji, który ma ten efekt.
-o StrictHostKeyChecking=no
Część zapobiega polecenie braku w przypadku, gdy nie podłączonego do docelowego gospodarza. (Zwykle SSH czekałby następnie na dane wejściowe użytkownika, aby potwierdzić próbę połączenia. My i tak kontynuujemy.)
sshpass
oczekuje polecenia ssh
lub scp
jako ostatniego argumentu. Musimy więc przepisać typowe wget -O - … | ssh …
polecenie do postaci bez potoku, jak wyjaśniono tutaj .
Na podstawie przepisu @Chris Snow. Wprowadziłem kilka ulepszeń:
Oto kod:
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}
nie jest określona.
tag
zmienna ma poprawny zestaw, teraz działa dobrze.
gawk