วิธีการดาวน์โหลดไฟล์โดยใช้เพียงแค่ทุบตีและไม่มีอะไรอื่น (ไม่มี curl, wget, perl ฯลฯ )


40

ฉันมีหัวขาดน้อยที่สุด * ซึ่งไม่มียูทิลิตี้บรรทัดคำสั่งใด ๆ สำหรับการดาวน์โหลดไฟล์ (เช่นไม่มี curl, wget เป็นต้น) ฉันทุบตีเท่านั้น

ฉันจะดาวน์โหลดไฟล์ได้อย่างไร

เป็นการดีที่ฉันต้องการวิธีการแก้ปัญหาที่จะทำงานในหลากหลาย * ระวัง


แล้วgawk
Neil McGuigan

ผมจำไม่ได้ตอนนี้ถ้าเพ่งพิศก็มี แต่ผมชอบที่จะเห็นการแก้ปัญหาตามเพ่งพิศถ้าคุณมีหนึ่ง :)
คริสหิมะ

1
นี่คือตัวอย่าง: gnu.org/software/gawk/manual/gawkinet/gawkinet.html#Web-page
Neil McGuigan

คำตอบ:


64

หากคุณมี bash 2.04 หรือสูงกว่าเมื่อ/dev/tcpเปิดใช้งานอุปกรณ์หลอกคุณสามารถดาวน์โหลดไฟล์จาก 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ประสงค์ trashes ทับขวาและช่องว่างชั้นนำ
  • Bash ไม่สามารถจัดการกับ NUL ไบต์ได้เป็นอย่างดีดังนั้นไฟล์ไบนารีจะหมด
  • unquoted $lineจะ glob

8
ดังนั้นคุณตอบคำถามของคุณเองในเวลาเดียวกันกับที่คุณถาม นั่นเป็นเครื่องจับเวลาที่น่าสนใจที่คุณมี;)
Meer Borg

11
@MeerBorg - เมื่อคุณถามคำถามให้มองหาช่องทำเครื่องหมาย 'ตอบคำถามของคุณเอง' - blog.stackoverflow.com/2011/07/…
Chris Snow

@eestartup - ฉันไม่คิดว่าคุณสามารถลงคะแนนสำหรับคำตอบของคุณเอง ฉันสามารถอธิบายรหัสได้หรือไม่ ยัง! แต่มันทำงานบน cygwin
Chris Snow

3
เพียงทราบ: สิ่งนี้จะไม่ทำงานกับการกำหนดค่าบางอย่างของ Bash ฉันเชื่อว่า Debian กำหนดค่าคุณลักษณะนี้จากการแจกจ่าย Bash ของพวกเขา

1
ถึงแม้ว่านี่จะเป็นเคล็ดลับที่ดี แต่ก็อาจทำให้การดาวน์โหลดเสียหายได้ง่ายเกินไป while readเช่นเดียวกับที่ทิ้งแบ็กสแลชและช่องว่างนำหน้าและ Bash ไม่สามารถจัดการกับ NUL ไบต์ได้เป็นอย่างดีดังนั้นไฟล์ไบนารีจะหมด และ$lineจะไม่พูดถึงเลย ... สิ่งนี้ไม่ได้เห็นในคำตอบ
ilkkachu

19

ใช้คม

เป็นเรื่องปกติสำหรับ 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

5
OP มี "* nix ซึ่งไม่มียูทิลิตีบรรทัดคำสั่งใด ๆ สำหรับการดาวน์โหลดไฟล์" ดังนั้นจึงไม่ต้องใช้คมอย่างแน่นอน
Celada

2
หมายเหตุlynx -sourceใกล้เข้ามาแล้ว
Steven Penny

สวัสดีนี่เป็นความคิดเห็นที่ล่าช้า แต่คุณจะบันทึกผลลัพธ์ของคำสั่ง telnet ลงในไฟล์ได้อย่างไร การเปลี่ยนเส้นทางด้วย ">" ส่งออกทั้งเนื้อหาของไฟล์และเอาต์พุต telnet เช่น "กำลังลอง 93.184.216.34 ... เชื่อมต่อกับ www.example.com" ฉันอยู่ในสถานการณ์ที่สามารถใช้ telnet ได้เท่านั้นฉันกำลังพยายามทำคุก chroot โดยมีกรอบน้อยที่สุดเท่าที่จะเป็นไปได้
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

สิ่งนี้จะไม่จัดการกับไฟล์การถ่ายโอนแบบไบนารี่ - มันจะล้มเหลวบนไบต์ที่ไม่มีค่า
สัญลักษณ์ตัวแทน

@ Wildcard ฉันไม่เข้าใจฉันได้แก้ไขด้วยตัวอย่างการถ่ายโอนไฟล์ไบนารี (มีค่า null bytes) คุณช่วยชี้ให้ฉันดูว่าฉันพลาดอะไรไปได้บ้าง
131

2
@Wildcard, heheh catใช่ที่ดูเหมือนว่ามันควรจะทำงานเพราะมันอ่านไฟล์ข้อมูลที่เกิดขึ้นจริงกับ ผมไม่แน่ใจว่าที่โกง (ตั้งแต่มันไม่ได้อย่างหมดจดเปลือก) หรือวิธีการแก้ปัญหาที่ดี (เนื่องจากcatเป็นเครื่องมือที่มีมาตรฐานหลังจากทั้งหมด) แต่ @ 131 คุณอาจต้องการเพิ่มบันทึกเกี่ยวกับสาเหตุที่ทำงานได้ดีกว่าโซลูชันอื่น ๆ ที่นี่
ilkkachu

@ Wildcard ฉันได้เพิ่มวิธีการทุบตีบริสุทธิ์เช่นกันเป็นคำตอบด้านล่าง และใช่โกงหรือไม่นี่เป็นวิธีการแก้ปัญหาที่ถูกต้องและคุ้มค่า upvote :)
ilkkachu

7

รับ " เพียงแค่ทุบตีและไม่มีอะไรอื่น " อย่างเคร่งครัดนี่คือการปรับคำตอบก่อนหน้านี้ ( @ 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 และส่งกลับจริงถ้าพบหนึ่งเท็จถ้ามันไม่ได้ Bash ไม่สามารถจัดการ NUL ไบต์ในสตริงได้ดังนั้นเมื่อreadส่งคืนด้วยจริงเราเพิ่ม NUL ไบต์ด้วยตนเองเมื่อพิมพ์และเมื่อมันคืนเท็จเรารู้ว่าไม่มี NUL ไบต์อีกต่อไปและควรเป็นข้อมูลชิ้นสุดท้าย .

ทดสอบกับ Bash 4.4 ในไฟล์ที่มี NUL อยู่ตรงกลางและลงท้ายด้วยศูนย์หนึ่งหรือสอง NUL และด้วยwgetและcurlไบนารีจาก Debian wgetไบนารีขนาด 373 kB ใช้เวลาดาวน์โหลดประมาณ 5.7 วินาที ความเร็วประมาณ 65 kB / s หรือมากกว่า 512 kb / s

ในการเปรียบเทียบการแก้ปัญหาของแมวที่ 131 นั้นเสร็จในเวลาน้อยกว่า 0.1 วินาทีหรือเร็วกว่าเกือบร้อยเท่า ไม่น่าแปลกใจมากจริงๆ

เห็นได้ชัดว่ามันโง่เนื่องจากไม่มีการใช้ยูทิลิตี้ภายนอกมีหลายสิ่งที่เราสามารถทำได้กับไฟล์ที่ดาวน์โหลดมา


ไม่ได้สะท้อนเสียงกระสุน - แบบสแตนด์อโลน - ไม่ใช่หรือ (: p)
131

1
@ 131 ไม่! Bash มีechoและprintfเป็น buildins (จำเป็นต้องมีprintfprintf -v
buildin

4

หากคุณมีแพ็คเกจนี้ libwww-perl

คุณสามารถใช้:

/usr/bin/GET

เมื่อพิจารณาว่าคำตอบอื่น ๆ ไม่เคารพข้อกำหนดของคำถาม (ทุบตีเท่านั้น) ฉันคิดว่านี่ดีกว่าlynxโซลูชันจริง ๆเพราะ Perl มีแนวโน้มที่จะติดตั้ง Lynx อย่างแน่นอน
มาร์คัส

4

ใช้การอัปโหลดแทนผ่าน SSH จากเครื่องท้องถิ่นของคุณ

กล่อง "หัวขาดน้อยที่สุด * ระวัง" หมายความว่าคุณอาจเป็น SSH ดังนั้นคุณสามารถใช้ SSH เพื่ออัปโหลดได้ ซึ่งเทียบเท่ากับการดาวน์โหลด (เช่นชุดซอฟต์แวร์ ฯลฯ ) ยกเว้นเมื่อคุณต้องการให้คำสั่งดาวน์โหลดรวมอยู่ในสคริปต์บนเซิร์ฟเวอร์ที่ไม่มีหัวของคุณ

ดังที่แสดงในคำตอบนี้คุณจะใช้งานสิ่งต่อไปนี้บนเครื่องโลคัลของคุณเพื่อวางไฟล์บนเซิร์ฟเวอร์ headless ระยะไกลของคุณ:

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

อัปโหลดได้เร็วขึ้นผ่าน SSH จากเครื่องที่สาม

ข้อเสียของวิธีการแก้ปัญหาข้างต้นเมื่อเปรียบเทียบกับการดาวน์โหลดนั้นเป็นความเร็วในการถ่ายโอนที่ต่ำกว่าเนื่องจากการเชื่อมต่อกับเครื่องของคุณมักจะมีแบนด์วิดท์น้อยกว่าการเชื่อมต่อระหว่างเซิร์ฟเวอร์ headless ของคุณและเซิร์ฟเวอร์อื่น ๆ

เพื่อแก้ปัญหานั้นคุณสามารถดำเนินการคำสั่งข้างต้นบนเซิร์ฟเวอร์อื่นที่มีแบนด์วิดท์ที่เหมาะสม ที่จะทำให้สะดวกสบายมากขึ้น (หลีกเลี่ยงการเข้าสู่ระบบคู่มือบนเครื่องที่สาม) ที่นี่เป็นคำสั่งในการดำเนินการในเครื่องท้องถิ่นของคุณ

เพื่อความปลอดภัยให้คัดลอกและวางคำสั่งนั้นรวมถึงอักขระช่องว่างนำ ' 'หน้า ดูคำอธิบายด้านล่างด้วยเหตุผล

 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 ของสาธารณะ - ก็ยังคงเกิดขึ้นกับบางผู้ให้บริการโฮสติ้งที่ใช้ร่วมกันโดยเฉพาะอย่างยิ่งเป็นเจ้าภาพยุโรป เพื่อให้กระบวนการยังคงเป็นไปโดยอัตโนมัติเราพึ่งพาsshpassเพื่อให้สามารถระบุรหัสผ่านในคำสั่ง มันจะต้องsshpassมีการติดตั้งบนโฮสต์ระดับกลางของคุณ ( sudo apt-get install sshpassภายใต้ Ubuntu)

  • เราพยายามใช้sshpassในวิธีที่ปลอดภัย แต่ก็ยังไม่ปลอดภัยเท่ากับกลไก pubkey SSH (พูดman sshpass) โดยเฉพาะอย่างยิ่งเราจัดหารหัสผ่าน SSH ไม่ได้เป็นอาร์กิวเมนต์บรรทัดคำสั่ง แต่ผ่านไฟล์ซึ่งถูกแทนที่ด้วยการทดแทนกระบวนการทุบตีเพื่อให้แน่ใจว่ามันไม่เคยมีอยู่บนดิสก์ printfเป็นทุบตีในตัวการทำให้แน่ใจว่าเป็นส่วนหนึ่งของรหัสนี้ไม่ปรากฏขึ้นเป็นคำสั่งที่แยกต่างหากในpsการส่งออกเป็นที่จะเปิดเผยรหัสผ่าน [ แหล่ง ] ฉันคิดว่าการใช้งานนี้sshpassมีความปลอดภัยเทียบเท่ากับsshpass -d<file-descriptor>ตัวแปรที่แนะนำman sshpassเนื่องจากทุบตีแมปภายในกับตัวให้/dev/fd/*คำอธิบายไฟล์ และไม่ต้องใช้ไฟล์ temp [ แหล่งที่มา] แต่ไม่มีการรับประกันบางทีฉันอาจมองข้ามบางสิ่งบางอย่าง

  • อีกครั้งเพื่อให้การsshpassใช้งานปลอดภัยเราต้องป้องกันไม่ให้คำสั่งบันทึกลงในประวัติทุบตีบนเครื่องของคุณ สำหรับสิ่งนั้นคำสั่งทั้งหมดจะถูกเติมด้วยอักขระเว้นวรรคหนึ่งตัวซึ่งมีผลกระทบนี้

  • -o StrictHostKeyChecking=noส่วนป้องกันไม่ให้คำสั่งจากความล้มเหลวในกรณีที่มันไม่เคยเชื่อมต่อกับพื้นที่เป้าหมาย (โดยปกติแล้ว SSH จะรอให้ผู้ใช้ป้อนข้อมูลเพื่อยืนยันความพยายามในการเชื่อมต่อเราจะดำเนินการต่อไป)

  • sshpassคาดว่า a sshหรือscpคำสั่งเป็นอาร์กิวเมนต์สุดท้าย ดังนั้นเราจึงมีการเขียนโดยทั่วไปwget -O - … | ssh …คำสั่งในรูปแบบโดยไม่ต้องท่อทุบตีเป็นตามที่อธิบายไว้ที่นี่


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.