{"id":6,"date":"2008-03-28T21:06:59","date_gmt":"2008-03-28T20:06:59","guid":{"rendered":"http:\/\/www.hughdenman.com\/blog\/?p=6"},"modified":"2008-12-20T23:35:13","modified_gmt":"2008-12-20T22:35:13","slug":"scp-resume-for-downloading-multiple-files","status":"publish","type":"post","link":"https:\/\/www.hughdenman.com\/blog\/?p=6","title":{"rendered":"scp-resume for downloading multiple files"},"content":{"rendered":"<p>You need to transfer a lot of files across a slightly temperamental ssl connection. You want something like a recursive scp command that supports resuming and will keep on trying until it gets the job done.<\/p>\n<p>rsync is <a href=\"http:\/\/joen.dk\/wordpress\/?p=34\">ideal<\/a> for this purpose &#8211; however, I find it quite <a href=\"http:\/\/www.cygwin.com\/ml\/cygwin\/2003-10\/msg01807.html\">dodgy under cygwin<\/a>, especially when transferring large files.<\/p>\n<p>A sweet alternative is <a href=\"http:\/\/www.cis.upenn.edu\/~bcpierce\/unison\/\">Unison<\/a>, for synchronizing filesets over ssh.<\/p>\n<p>However, I often find myself falling back on a nice script called <a href=\"http:\/\/ejtaal.net\/scripts-showcase\/#scp-resume\">scp-resume.sh<\/a> designed for resuming the transfer of large files using dd over ssh. We can invoke this script inside a loop to transfer lots of files at a time.<\/p>\n<p>One problem with the script is the use of the construct below to determine file sizes:<\/p>\n<p><code>localsize=`ls -l \"${localfile}\" | awk '{ print $5 }'`<\/code><\/p>\n<p>This will fail if there are spaces in the username of the file owner. Most likely you&#8217;ll get:<\/p>\n<pre>\r\nResuming download of [file] at byte None\r\n...\r\ndd: invalid number `None'<\/pre>\n<p>where the group owning the file is reported by cygwin as &#8216;None&#8217;. The fix is to replace every instance of this <code>ls -l<\/code> construct with something like <code>localsize=`ls -g \"${localfile}\" | awk '{ print $4 }'`<\/code>. The <code>-g<\/code> option displays the file size but not the owner name, so you should be safe from spaces confusing awk. I don&#8217;t know if the -g option is POSIX, but it&#8217;s in GNU ls anyway.<\/p>\n<p>You might be tempted to use <code>ls -s<\/code>, but this reports the amount of disk space used, rather than the actual length of the file (i.e. it will be a multiple of the allocation blocks). You can see the difference using <code>ls -ls<\/code>:<\/p>\n<pre>\r\nHugh Denman@gpplap3 ~\r\n$ cat &gt; asd.txt\r\nfre\r\nhschui\r\nhuernui\r\n\r\nHugh Denman@gpplap3 ~\r\n$ ls -ls --block-size=1 .\/asd.txt\r\n1024 -rw-r--r-- 1 Hugh Denman None 19 Mar 28 17:59 .\/asd.txt<\/pre>\n<p>Here my 19-byte text file is taking up 1024 bytes of disk space.<\/p>\n<p>Two other possibilities, suggested by Erik Jan Taal, are <code>perl -e \"print -s '$filename'\"<\/code> and <code>ls -l | sed -n 's\/.* [^0-9]*\\([0-9]\\+\\) .*\/\\1\/ p'<\/code>. These will work on FreeBSD, for example, which does not support <code>ls -g<\/code>.<\/p>\n<p>To use the <code>scp-resume<\/code> script, we&#8217;ll need a text file containing the filenames to transfer from the remote machine. Here&#8217;s one way to generate this list.<br \/>\n<code>$ ssh remote-user@remote.machine.ip.addr \"\/bin\/find \/cygdrive\/d -type f\" | grep -vi i386 &gt; .\/filelist.txt<\/code><br \/>\nIn this example, the remote drive contains the OS installation files in <code>\/cygdrive\/d\/I386<\/code>, which we don&#8217;t want to transfer.<\/p>\n<p>With a fixed <code>scp-resume<\/code> script, and the list of files to transfer present, all that&#8217;s left to do is iterate over each file in the list and tell scp-resume to download it. We use the <code>cat filelist.txt | while read FILE<\/code> approach because it will preserve spaces in the filename (unlike <code>for file in `cat filelist.txt`<\/code>).<\/p>\n<pre>\r\ncat filelist.txt | while read FILE ; do\r\nDIR=`dirname \"$FILE\"`;\r\nmkdir -p \".\/$DIR\" ;\r\n.\/scp-resume.sh -d \"<code>remote-user@remote.machine.ip.addr<\/code>:$FILE\" \".\/$FILE\" ;\r\ndone<\/pre>\n<p>This very nearly works &#8211; the only trouble is that it will only transfer the first file in the list, and then inexplicably stops without an error! This is a difficulty that arises whenever you use the <code>cat [file] | while read VAR<\/code> idea, with a shell invocation inside the while loop: whenever a shell is started, it gets STDIN, which kills the pipe (I found that out in a <a href=\"http:\/\/groups.google.ie\/group\/comp.unix.shell\/browse_thread\/thread\/9d6d34a73f64d405\/422901d541943403?lnk=st&amp;q=shell+terminates+for+loop#422901d541943403\">Usenet post<\/a>). So we have to modify <code>scp-resume<\/code> one last time, changing the download command<\/p>\n<pre>\r\nssh -C -c arcfour \"$userhost\" \"dd bs=1 skip=$localsize \\\"if=${remotefile}\\\"\" &gt;&gt; $localfile &lt; \/dev\/null<\/pre>\n<p>With this change, you can&#8217;t enter the ssh password in manually &#8211; but you&#8217;d have to have automatic authentication setup anyway really, as you don&#8217;t want to enter your password for every file. A simple way to set up automatic authentication is described <a href=\"http:\/\/magicmonster.com\/kb\/net\/ssh\/auto_login.html\">here<\/a>.<\/p>\n<p>Lastly, you can wrap the whole command above in a for loop with a few iterations so that if the connection is dropped on a few transfers, the file can be resumed in a subsequent pass:<\/p>\n<pre>\r\nfor i in `seq 0 100`; do\r\ncat filelist.txt | while read FILE ; do\r\nDIR=`dirname \"$FILE\"`;  mkdir -p \".\/$DIR\" ;\r\n.\/scp-resume.sh -d \"<code>remote-user@remote.machine.ip.addr<\/code>:$FILE\" \".\/$FILE\" ;\r\ndone; done<\/pre>\n<p>This whole process is hideously inefficient for large numbers of files, alas. But it seems to get the job done. Here&#8217;s my edited version of scp-resume, using the redirect from \/dev\/null for ssh and using <code>ls -g<\/code> instead of <code>ls -l<\/code> to query the file size. Note that I&#8217;ve only tested the downloading functionality, never the uploading bits.<\/p>\n<pre>\r\n#!\/bin\/sh\r\n#\r\n# scp-resume - by erik jan taal\r\n# http:\/\/ejtaal.net\/scripts-showcase\/#scp-resume\r\n# Speed improvements by using blocks by nitro.tm@gmail.com\r\n# Fixed by Hugh Denman to use ls -g (safe with usernames containing spaces)\r\n#   this versions assumes that ssh is setup for automatic authentication rather than manual password entry\r\n#\r\n# This script assumes that you have access to the 'dd' utility\r\n# on both the local and remote host.\r\n\r\n# dd transfer blocksize (8192 by default)\r\nblocksize=8192\r\n\r\nusage()\r\n{\r\n  echo\r\n  echo \"Usage: `basename $0` -u(pload)   $localfile  $remotefile [$sshargs]\"\r\n  echo \"       `basename $0` -d(ownload) $remotefile $localfile  [$sshargs]\"\r\n  echo\r\n  echo \"  $remotefile should be in the scp format, i.e.: [user@]host:filename\"\r\n  echo \"  $sshargs are option further ssh options such as a port specification\"\r\n  echo \"     (-p 1234) or use of compression (-C)\"\r\n  echo\r\n  echo \"  -u:\"\r\n  echo \"     $remotefile may be [user@]host: for uploading to your remote home directory\"\r\n  echo \"  -d:\"\r\n  echo \"     $localfile may be a period (.) when downloading a remote file to the\"\r\n  echo \"       current working directory.\"\r\n  echo\r\n  exit 1\r\n}\r\n\r\n[ -z \"$1\" -o -z \"$2\" -o -z \"$3\" ] &amp;&amp; usage\r\n\r\noption=$1\r\ncase $option in\r\n  -[uU]*)\r\n    localfile=$2\r\n    remote=$3\r\n    shift 3\r\n    sshargs=\"$*\"\r\n\r\n    userhost=${remote%:*}\r\n    remotefile=${remote#*:}\r\n\r\n    if [ ! -f \"$localfile\" ]; then\r\n      echo \"!! File not found: $localfile\"\r\n      usage\r\n    fi\r\n    if [ x\"$userhost\" = x\"$remote\" ]; then usage; fi\r\n    if [ x\"$remotefile\" = x\"$remote\" -o -z \"$remotefile\" ]; then remotefile=`basename \"$localfile\"`; fi\r\n\r\n    echo \"==&gt;&gt; Getting size of remote file:\"\r\n    localsize=`ls -g \"${localfile}\" | awk '{ print $4 }'`\r\n    remotesize=`ssh $sshargs \"$userhost\" \"[ -f \\\"${remotefile}\\\" ] &amp;&amp; ls -g \\\"${remotefile}\\\"\" | awk '{ print $4 }' &lt; \/dev\/null`\r\n\r\n    [ -z \"$remotesize\" ] &amp;&amp; remotesize=0\r\n    echo \"=&gt; Remote filesize: $remotesize bytes\"\r\n\r\n    if [ $localsize -eq $remotesize ]; then\r\n      echo \"=&gt; Local size equals remote size, nothing to transfer.\"\r\n      exit 0;\r\n    fi\r\n\r\n    remainder=$((remotesize % blocksize))\r\n    restartpoint=$((remotesize - remainder))\r\n    blockstransferred=$((remotesize \/ blocksize))\r\n\r\n    echo \"=&gt; Resuming upload of '$localfile'\"\r\n    echo \"  at byte: $restartpoint ($blockstransferred blocks x $blocksize bytes\/block),\"\r\n    echo \"  will overwrite the trailing $remainder bytes.\"\r\n\r\n    dd bs=$blocksize skip=$blockstransferred \"if=${localfile}\" |\r\n      ssh $sshargs \"$userhost\" \"dd bs=$blocksize seek=$blockstransferred of=\\\"$remotefile\\\"\" &lt; \/dev\/null\r\n\r\n    echo \"done.\"\r\n    ;;\r\n  -[dD]*)\r\n    localfile=$3\r\n    remote=$2\r\n    shift 3\r\n    sshargs=\"$*\"\r\n\r\n    userhost=${remote%:*}\r\n    remotefile=${remote#*:}\r\n\r\n    if [ x\"$localfile\" = x\".\" ]; then localfile=`basename \"$remotefile\"`; fi\r\n    if [ ! -f \"$localfile\" ]; then\r\n      localsize=0;\r\n    else\r\n      localsize=`ls -g \"${localfile}\" | awk '{ print $4 }'`\r\n    fi\r\n    [ x\"$remotefile\" = x\"$remote\" ] &amp;&amp; usage\r\n    [ -z \"$localsize\" ] &amp;&amp; localsize=0\r\n\r\n    remainder=$((localsize % blocksize))\r\n    restartpoint=$((localsize - remainder))\r\n    blockstransferred=$((localsize \/ blocksize))\r\n\r\n    echo \"=&gt; Resuming download of '$localfile'\"\r\n    echo \"  at byte: $restartpoint ($blockstransferred blocks x $blocksize bytes\/block)\"\r\n    echo \"  filesize: $localsize; will overwrite the trailing $remainder bytes.\"\r\n    ssh $sshargs \"$userhost\" \"dd bs=$blocksize skip=$blockstransferred \\\"if=${remotefile}\\\"\" &lt; \/dev\/null |\r\n      dd bs=$blocksize seek=$blockstransferred \"of=$localfile\"\r\n\r\n    ;;\r\n  *)\r\n    usage\r\n    ;;\r\nesac<\/pre>\n<p>Second real post exactly one year after the first! Prolific.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You need to transfer a lot of files across a slightly temperamental ssl connection. You want something like a recursive scp command that supports resuming and will keep on trying until it gets the job done. rsync is ideal for this purpose &#8211; however, I find it quite dodgy under cygwin, especially when transferring large [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[],"_links":{"self":[{"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6"}],"collection":[{"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=6"}],"version-history":[{"count":0,"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=6"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=6"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.hughdenman.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=6"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}