bash 만 사용하고 파일을 다운로드하는 방법 (컬, wget, perl 등 없음)


40

파일을 다운로드하기위한 명령 줄 유틸리티 (예 : curl, wget 등) 없는 최소 헤드리스 * nix 가 있습니다 . 나는 배쉬 만 가지고있다.

파일을 어떻게 다운로드합니까?

이상적으로는 광범위한 * nix에서 작동하는 솔루션을 원합니다.


어떻 gawk
Neil McGuigan

gawk를 사용할 수 있다면 지금은 기억이 나지 않습니다.하지만 gawk 기반 솔루션을 갖고 싶다면 :)
Chris Snow

답변:


64

/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 명령 줄을 통해 패키지를 업그레이드하고 설치합니까?

업데이트 : 의견에서 언급했듯이 위에서 설명한 접근 방식은 간단합니다.

  • read의지는 백 슬래시 선도 공백을 삭제 한 사용자.
  • Bash는 NUL 바이트를 아주 잘 처리 할 수 ​​없으므로 바이너리 파일이 없습니다.
  • 따옴표 $line가 붙지 않습니다.

8
그래서 당신은 당신이 질문과 동시에 자신의 질문에 대답했다. 그것은 당신이 가진 재미있는 타임머신입니다;)
Meer Borg

11
@MeerBorg-질문 할 때 '자신의 질문에 답하십시오'확인란을 찾으십시오 -blog.stackoverflow.com/2011/07/…
Chris Snow

@eestartup-나는 당신이 당신의 자신의 답변에 투표 할 수 있다고 생각하지 않습니다. 코드를 설명 할 수 있습니까? 아직! 그러나 그것은 cygwin에서 작동합니다.
Chris Snow

3
참고 사항 : 일부 Bash 구성에서는 작동하지 않습니다. 데비안은 Bash 배포 에서이 기능을 구성한다고 생각합니다.

1
어이, 이것은 좋은 방법이지만 너무 쉽게 다운로드를 손상시킬 수 있습니다. while read쓰레기와 같은 백 슬래시와 선행 공백 및 Bash는 NUL 바이트를 아주 잘 처리 할 수 ​​없으므로 바이너리 파일이 없습니다. 그리고 따옴표 $line가 붙지 않을 것입니다.
ilkkachu

19

살 lyn이를 사용하십시오.

대부분의 유닉스 / 리눅스에서 일반적입니다.

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

-dump : 첫 번째 파일을 stdout에 덤프하고 종료

man lynx

또는 netcat :

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

또는 텔넷 :

(echo 'GET /'; echo ""; sleep 1; ) | telnet www.google.com 80

5
OP에는 "* nix에는 파일 다운로드를위한 명령 줄 유틸리티가 없습니다"가 있으므로 살 lyn 이는 없습니다.
Celada

2
메모 lynx -source는 wget에 더 가깝습니다
Steven Penny

이봐, 이것은 정말 늦은 의견이지만 어떻게 telnet 명령의 출력을 파일에 저장합니까? ">"로 리디렉션하면 "Trying 93.184.216.34 ... Connected to www.example.com."과 같은 파일 내용과 텔넷 출력이 모두 출력됩니다. 나는 텔넷 만 사용할 수있는 상황에 처해 있습니다. 최소한의 프레임 워크로 chroot jail을 만들려고합니다.
pixelomer

10

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>&-
}
  • 읽지 않기 위해 && 고양이를 깰
  • http 1.0을 사용하므로 연결을 기다리거나 보낼 필요가 없습니다.

이진 파일을 다음과 같이 테스트 할 수 있습니다

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

이진 전송 파일은 처리하지 않으며 null 바이트에서는 실패합니다.
와일드 카드

@Wildcard, 이해가 안됩니다. 이진 파일 전송 예제 (널 바이트 포함)로 편집했습니다. 내가 누락 된 부분을 알려 주시겠습니까?
131

2
@Wildcard, heheh, 그렇습니다 cat. 실제 파일 데이터를로 읽으므로 작동해야합니다 . 그것이 부정 행위인지 ( 순전히 쉘 이 아니기 때문에 ) 또는 좋은 해결책 ( cat결국 표준 도구 이기 때문에) 인지 확실하지 않습니다 . 그러나 @ 131에서는 다른 솔루션보다 왜 더 나은지에 대한 메모를 추가 할 수 있습니다.
ilkkachu

@Wildcard, 나는 순수한 bash 솔루션을 아래 답변으로 추가했습니다. 그리고 네, 부정 행위 여부는, 이것은 유효한 솔루션이며
공짜

7

은 "촬영 단지 강타하고 아무것도 다른 사람을 "엄격하게, 여기에 (이전 답변 중 하나 적응의 크리스의 @ , 131 개의 @ 외부 유틸리티 (심지어 표준 것들)뿐만 아니라 바이너리 파일과 함께 작동을 호출하지 않습니다)

#!/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 바이트를 처리 할 수 ​​없으므로 readtrue로 반환하면 인쇄 할 때 NUL 바이트를 수동으로 추가하고 false를 반환하면 더 이상 NUL 바이트가 없다는 것을 알고 있으며 이것이 마지막 데이터 여야 함 .

중간에 NUL이 있고 0, 1 또는 2 개의 NUL로 끝나는 파일과 데비안의 바이너리 wgetcurl바이너리 로 Bash 4.4로 테스트했습니다 . 373kB wget바이너리를 다운로드하는 데 약 5.7 초가 걸렸습니다. 약 65 kB / s의 속도 또는 512 kb / s 이상의 비트.

이에 비해 @ 131의 cat-solution은 0.1 초 미만 또는 거의 백배 더 빠르게 완료됩니다. 정말 놀라운 일이 아닙니다.

외부 유틸리티를 사용하지 않으면 다운로드 한 파일로 수행 할 수있는 작업이 많지 않고 실행 파일로 만들 수 없기 때문에 이는 명백히 어리석은 일입니다.


독립형 비 쉘 바이너리가 아닌가? (: p)
131

1
@ 131, 안돼! 배쉬가 echoprintf내장 명령으로 (그것은 내장 필요 printf구현을 printf -v)
ilkkachu

4

이 패키지가있는 경우 libwww-perl

간단하게 사용할 수 있습니다 :

/usr/bin/GET

다른 답변이 질문 요구 사항을 존중하지 않는다는 것을 고려할 때 (Bash 전용) lynxPerl이 Lynx에 사전 설치되어 있기 때문에 이것이 실제로 솔루션 보다 낫다고 생각합니다 .
마커스

4

로컬 컴퓨터에서 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통해 파일을 다운로드 한 다음 SSH wgettarget-host통해 파일을 업로드하기 시작 합니다. 다운로드 및 업로드는 대역폭을 사용하며 intermediate-host동시에 Bash 파이프와 동등한 기능으로 인해 진행되므로 진행 속도가 빠릅니다.

  • 이를 사용할 때는 두 개의 서버 로그인 ( user@*-host), 대상 호스트 비밀번호 ( yourpassword), 다운로드 URL ( http://example.com/…) 및 대상 호스트 ( /path/to/output-file.zip) 의 출력 경로를 적절한 자체 값 으로 바꿔야 합니다.

  • 를 들어 -T -e none이 파일을 전송하는 데 사용하는 SSH 옵션, 볼 이 자세한 설명을 .

  • 이 명령은 SSH의 공개 키 인증 메커니즘을 사용할 수없는 경우를위한 것으로, 일부 공유 호스팅 공급자, 특히 Host Europe에서 여전히 발생합니다 . 프로세스를 계속 자동화하기 sshpass위해 명령에 비밀번호를 제공 할 수 있어야합니다. sshpass중간 호스트 ( sudo apt-get install sshpassUbuntu 아래) 에 설치 해야 합니다 .

  • 우리 sshpass는 안전한 방식 으로 사용하려고 하지만 SSH pubkey 메커니즘만큼 안전하지는 않습니다 (예 :) man sshpass. 특히 SSH 암호를 명령 줄 인수가 아니라 파일을 통해 제공합니다.이 암호는 디스크에 존재하지 않도록 bash 프로세스 대체로 대체됩니다. 는 printf확인 코드의이 부분에 별도의 명령으로 팝업하지 않습니다 만드는 내장 떠들썩한이며, ps그 암호 [노출 될 수로 출력 소스 ]. 나는 생각 이 사용하는 것이 sshpass단지와 같이 안전 sshpass -d<file-descriptor>에 추천 변형 man sshpassbash는 이러한 내부적으로 매핑하기 때문에, /dev/fd/*어쨌든 파일 기술자. 그리고 임시 파일을 사용하지 않고 [그 소스]. 그러나 보장 할 수는 없습니다. 어쩌면 내가 간과했을 수도 있습니다.

  • 다시 sshpass안전하게 사용하려면 로컬 컴퓨터의 bash 기록에 명령이 기록되지 않도록해야합니다. 이를 위해 전체 명령 앞에 하나의 공백 문자가 붙어있어이 효과가 있습니다.

  • -o StrictHostKeyChecking=no부분은 대상 호스트에 연결되지 않을 경우에는 실패하는 명령을 방지한다. (일반적으로 SSH는 연결 시도를 확인하기 위해 사용자 입력을 기다립니다. 어쨌든 계속 진행합니다.)

  • sshpass마지막 인수로 ssh또는 scp명령을 기대합니다 . 따라서 여기wget -O - … | ssh … 에서 설명하는 것처럼 일반적인 명령을 bash 파이프가없는 형태로 다시 작성해야합니다 .


3

@Chris Snow 레시피를 기반으로합니다. 나는 약간의 개선을했다 :

  • http 체계 검사 (http 만 지원)
  • http 응답 유효성 검사 (응답 상태 행 확인 및 '연결 : 닫기'가 아닌 '\ r \ n'행으로 헤더 및 본문 분할) 경우에 따라 사실이 아닙니다.
  • 200이 아닌 코드에서 실패했습니다 (인터넷에서 파일을 다운로드하는 것이 중요합니다)

코드는 다음과 같습니다.

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

멋진 개선 +1
Chris Snow

그것은 효과가 있었지만이 스크립트를 사용할 때 우려를 찾았습니다. 모든 데이터가 읽 히면 몇 초 동안 기다리십시오.이 사건은 @Chris Snow 답변에서 발생하지 않습니다.
zw963

그리고,이 대답에 echo -en "GET ${PATH} HTTP/1.1\r\nHost: ${HOST}\r\n${tag}\r\n\r\n" >&3, ${tag}지정되지 않았습니다.
zw963

tag변수가 올바른 세트 로이 답변을 편집하면 이제 잘 작동합니다.
zw963

zsh와 작동하지 않음, __wget google.com 죄송합니다. http / usr / bin / env 만 지원 : bash : 해당 파일 또는 디렉토리가 없습니다
vrkansagara
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.