como baixar um arquivo usando apenas bash e nada mais (sem curl, wget, perl, etc.)

39

Eu tenho um minúsculo * nix que não tem nenhum utilitário de linha de comando para fazer download de arquivos (por exemplo, sem curl, wget, etc). Eu só tenho bash.

Como posso baixar um arquivo?

Idealmente, gostaria de uma solução que funcionasse em uma ampla variedade de * nix.

    
por Chris Snow 22.07.2013 / 09:43

7 respostas

60

Se você tiver o bash 2.04 ou acima com o pseudo-dispositivo /dev/tcp habilitado, você pode baixar um arquivo do próprio bash.

Cole o seguinte código diretamente em um bash shell (você não precisa salvar o código em um arquivo para execução):

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>&-
}

Você pode executá-lo a partir do shell da seguinte forma:

__wget http://example.iana.org/

Fonte: a resposta de Moreaki atualizando e instalando pacotes através da linha de comando do cygwin?

Atualização: como mencionado no comentário, a abordagem descrita acima é simplista:

  • o read trash as barras invertidas e os principais espaços em branco.
  • O Bash não consegue lidar com bytes NUL muito bem, então os arquivos binários estão fora.
  • sem coquete $line será glob.
por 22.07.2013 / 09:43
19

Use lince.

É bastante comum para a maioria do Unix / Linux.

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

-dump: despeja o primeiro arquivo para stdout e saia

man lynx

Ou netcat:

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

Ou telnet:

(echo 'GET /'; echo ""; sleep 1; ) | telnet www.google.com 80
    
por 22.07.2013 / 17:49
10

Adaptado da resposta de Chris Snow Isso também pode manipular arquivos de transferência binários

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>&-
}
  • eu quebro & & gato para sair da leitura
  • eu uso o http 1.0, então não há necessidade de esperar / enviar uma conexão: fechar

Você pode testar arquivos binários como este

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
    
por 02.02.2018 / 00:08
7

Tomando o " apenas Bash e nada else " estritamente, aqui está uma adaptação de respostas anteriores ( @ Chris , @ 131 ) que não chama nenhum utilitário externo (nem mesmo os padrões), mas também funciona com arquivos binários:

#!/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='
#!/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='%pre%'
  while IFS= read -d '' -r x || { nul=""; [ -n "$x" ]; }; do 
      printf "%s$nul" "$x"
  done <&3
  exec 3>&-
}
' while IFS= read -d '' -r x || { nul=""; [ -n "$x" ]; }; do printf "%s$nul" "$x" done <&3 exec 3>&- }

Use com download http://path/to/file > file .

Lidamos com bytes NUL com read -d '' . Ele lê até um byte NUL e retorna true se encontrar um, false se não for encontrado. Bash não pode manipular bytes NUL em strings, então quando read retorna com true, nós adicionamos o byte NUL manualmente quando imprimimos, e quando retorna false, sabemos que não há mais bytes NUL, e este deve ser o último pedaço de dados.

Testado com o Bash 4.4 em arquivos com NULs no meio e terminando em zero, um ou dois NULs e também com os binários wget e curl do Debian. O binário 373 kB wget demorou cerca de 5,7 segundos para ser baixado. Uma velocidade de cerca de 65 kB / s ou um pouco mais de 512 kb / s.

Em comparação, a solução de gato da @ 131 termina em menos de 0,1 s, ou quase cem vezes mais rápido. Não é muito surpreendente, realmente.

Isto é obviamente bobo, já que sem usar utilitários externos, não há muito que possamos fazer com o arquivo baixado, nem mesmo torná-lo executável.

    
por 02.02.2018 / 11:32
4

Use o upload em vez disso, via SSH da sua máquina local

Uma caixa "minimal headless * nix" significa que você provavelmente é SSH. Então você também pode usar o SSH para fazer o upload para ele. O que é funcionalmente equivalente ao download (de pacotes de software etc.) exceto quando você deseja que um comando de download seja incluído em um script em seu servidor sem cabeçalho, é claro.

Como mostrado em esta resposta , você executaria o seguinte em sua máquina local para coloque um arquivo no seu servidor headless remoto:

wget -O - http://example.com/file.zip | ssh user@host 'cat >/path/to/file.zip'

Upload mais rápido via SSH de uma terceira máquina

A desvantagem da solução acima em comparação com o download é menor velocidade de transferência, uma vez que a conexão com sua máquina local geralmente tem muito menos largura de banda do que a conexão entre seu servidor headless e outros servidores.

Para resolver isso, você pode executar o comando acima em outro servidor com largura de banda decente. Para tornar isso mais confortável (evitando um login manual na terceira máquina), aqui está um comando para executar na sua máquina local .

Para ser seguro, copie & cole esse comando incluindo o caractere de espaço à esquerda ' ' . Veja as explicações abaixo para o motivo.

 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' \
"

Explicações:

  • O comando irá ssh para sua terceira máquina intermediate-host , iniciará o download de um arquivo para lá via wget e começará a carregá-lo para target-host via SSH. O download e o upload usam a largura de banda do seu intermediate-host e acontecem ao mesmo tempo (devido aos equivalentes do Bash pipe), portanto, o progresso será rápido.

  • Ao usar isso, você deve substituir os dois logons do servidor ( user@*-host ), a senha do host de destino ( yourpassword ), a URL de download ( http://example.com/… ) e o caminho de saída em seu host de destino ( /path/to/output-file.zip ) com valores próprios apropriados.

  • Para as opções -T -e none SSH ao usá-lo para transferir arquivos, consulte estas explicações detalhadas .

  • Este comando é destinado a casos em que você não pode usar o mecanismo de autenticação de chave pública do SSH - ainda acontece com alguns provedores de hospedagem compartilhada, nomeadamente a Host Europe . Para ainda automatizar o processo, contamos com sshpass para poder fornecer a senha no comando. Ele requer que sshpass seja instalado em seu host intermediário ( sudo apt-get install sshpass no Ubuntu).

  • Tentamos usar o sshpass de maneira segura, mas ele ainda não será tão seguro quanto o mecanismo de publicação do SSH (diz man sshpass ). Em particular, fornecemos a senha do SSH não como um argumento de linha de comando, mas através de um arquivo, que é substituído pela substituição do processo bash para garantir que nunca exista no disco. O printf é um bash integrado, garantindo que essa parte do código não apareça como um comando separado em ps output, pois isso exporia a senha [ source ]. Eu acho que esse uso de sshpass é tão seguro quanto a variante sshpass -d<file-descriptor> recomendada em man sshpass , porque o bash mapeia internamente para um descritor de arquivo /dev/fd/* assim mesmo. E isso sem usar um arquivo temporário [ source ]. Mas sem garantias, talvez eu tenha esquecido algo.

  • Novamente para tornar o uso de sshpass seguro, precisamos evitar que o comando seja gravado no histórico bash em sua máquina local. Para isso, todo o comando é prefixado com um caractere de espaço, o que tem esse efeito.

  • A parte -o StrictHostKeyChecking=no impede que o comando falhe caso nunca tenha se conectado ao host de destino. (Normalmente, o SSH esperaria pela entrada do usuário para confirmar a tentativa de conexão. Nós fazemos isso de qualquer maneira.)

  • sshpass espera um comando ssh ou scp como seu último argumento. Portanto, temos que reescrever o comando típico wget -O - … | ssh … em um formulário sem um tubo bash, conforme explicado aqui .

por 24.07.2016 / 23:29
3

Se você tem este pacote libwww-perl

Você pode simplesmente usar:

/usr/bin/GET
    
por 05.08.2013 / 23:43
3

Baseado na receita do @Chris Snow. Eu fiz algumas melhorias:

  • link
  • link
  • falhou no código não-200 (é importante fazer download de arquivos na internet)

Aqui está o código:

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>&-
}
    
por 16.05.2017 / 13:05