34

I need to download a file from a HTTP server, but only if it changed since the last time I downloaded it (e.g. via the If-Modified-Since header). I also need to use a custom name for the file on my disk.

What tool can I use for this task on linux?


wget -N cannot be used because -N cannot be used with -O.

3
  • Why not download the file and then rename it? Apr 30, 2015 at 22:50
  • .. because the tool still needs to be able to check if the HTTP resource changed since the last download? This will be hard if the file has been renamed and thus does not exist anymore at the place the tool expects it.
    – cweiske
    May 1, 2015 at 7:33
  • Sorry, I rushed that comment, see my answer. May 1, 2015 at 8:05

5 Answers 5

46

Consider using curl instead of wget:

curl -o "$file" -z "$file" "$uri"

man curl says:

-z/--time-cond <date expression>

(HTTP/FTP) Request a file that has been modified later than the given time and date, or one that has been modified before that time. The date expression can be all sorts of date strings or if it doesn't match any internal ones, it tries to get the time from a given file name instead.

If $file doesn't necessarily pre-exist, you'll need to make the use of the -z flag conditional, using test -e "$file":

if test -e "$file"
then zflag="-z '$file'"
else zflag=
fi
curl -o "$file" $zflag "$uri"

(Note that we don't quote the expansion of $zflag here, as we want it to undergo splitting to 0 or 2 tokens).

If your shell supports arrays (e.g. Bash), then we have a safer and cleaner version:

if test -e "$file"
then zflag=(-z "$file")
else zflag=()
fi
curl -o "$file" "${zflag[@]}" "$uri"
5
  • 4
    With curl 7.64.0, curl will just output a warning if the -z file doesn't exist yet, and then successfully download the file and exit. tl;dr, you don't need the zflag check if you can just ignore the warning.
    – Shane
    Sep 11, 2020 at 17:59
  • Yes, the test is not (or no longer) necessary, but still useful if you don't want warnings out of your scripts (e.g. as cron jobs). Nov 4, 2020 at 16:28
  • Is it possible to tell whether the file was actually updated from curl return codes or something like that?
    – nwod
    Mar 16, 2022 at 12:01
  • Not directly @nwod, but you could inspect the last-modified time of the file (perhaps even link the file to a new name before the fetch so that you can use test -nt afterwards). Mar 16, 2022 at 13:03
  • You can also just inspect stderr. If curl downloads a file, the last line of stderr starts with 100. If it skips downloading a file, the last line starts with ` 0`.
    – daviewales
    Mar 5 at 3:37
9

The wget switch -N only gets the file if it has changed so a possible approach would be to use the simple -N switch which will get the file if it needs to but leaves it with the wrong name. Then create a hard link using the ln -P command to link it to a "file" with the correct name. The linked file has the same metadata as the original.

The only limitation being that you cannot have hard links across file system boundaries.

3
  • 1
    For many purposes, a symbolic link may be adequate - unless inode identity actually matters for the asker. Mar 29, 2017 at 10:03
  • 3
    wget is the better tool for this job. It checks timestamp AND the file size, which curl (7.38.0) doesn't. Also, wget terminates with non-0 on 4xx/5xx, whereas curl doesn't really care about server-codes by default. May 16, 2017 at 22:26
  • wget leave the result with a new name that is unclear. it appends .1, or .2 , or .n. This is unappealing Aug 12, 2023 at 16:10
7

Python 3.5+ script for wrapping curl command:

import argparse
import pathlib

from subprocess import run
from itertools import chain

parser = argparse.ArgumentParser()
parser.add_argument('url')
parser.add_argument('filename', type=pathlib.Path)
args = parser.parse_args()

run(chain(
    ('curl', '-s', args.url),
    ('-o', str(args.filename)),
    ('-z', str(args.filename)) if args.filename.exists() else (),
))
1
  • This is awesome! TIL chain :)
    – John Oxley
    May 2, 2017 at 6:37
3

A similar approach to "date check" (with "curl --time-cond"), would be to download according to file size comparison, i.e. Download only if the local file has a different size than the remote file.

It is useful for example, when the download process failed in the middle, and thus the local downloaded file gets a newer date than the remote file, but it's actually corrupted, and re-downloading is required:

local_file_size=$([[ -f ${FILE_NAME} ]] && wc -c < ${FILE_NAME} || echo "0")
remote_file_size=$(curl -sI ${FILE_URL} | awk '/Content-Length/ { print $2 }' | tr -d '\r' )

if [[ "$local_file_size" -ne "$remote_file_size" ]]; then
    curl -o ${FILE_NAME} ${FILE_URL}
fi

The "curl -z / --time-cond" option (that was suggested in another answer) will not download the remote file in this case (cause the local file has a newer date), but this "size check" script will!

0

I tried various things with wget, and wasn't able to stop it truncating the output unless using " -N ".

Instead, you can cook your own -if-modified header, and replace truncated files with backups.

OUTFILE="some.thing"
IF_MOD_DATE=`date "+%a, %d %b %Y %T %Z" -r $OUTFILE`
IF_MOD_HEADER="If-Modified-Since: $IF_MOD_DATE"

cp $OUTFILE backup_$OUTFILE
wget -O $OUTFILE --header="$IF_MOD_HEADER" "http://your.tld/resource"

# if files is truncated, replace with backup
[ -s $OUTFILE ] || { rm $OUTFILE && mv backup_$OUTFILE $OUTFILE ; }

# remove any backup and ignore complaints of missing files.
rm backup_$OUTFILE 2>&1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .