summaryrefslogtreecommitdiff
path: root/Functions/Zftp
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-04-25 15:43:45 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-04-25 15:43:45 +0000
commite8eb43fc308acb3f1a8ebada7633c097e5050e46 (patch)
tree2df36ef3c7709c7d4d3b7bdcb5761f98649859bf /Functions/Zftp
parente74702b467171dbdafb56dfe354794a212e020d9 (diff)
downloadzsh-e8eb43fc308acb3f1a8ebada7633c097e5050e46.tar.gz
zsh-e8eb43fc308acb3f1a8ebada7633c097e5050e46.zip
Initial revision
Diffstat (limited to 'Functions/Zftp')
-rw-r--r--Functions/Zftp/README4
-rw-r--r--Functions/Zftp/zfanon70
-rw-r--r--Functions/Zftp/zfautocheck33
-rw-r--r--Functions/Zftp/zfcd52
-rw-r--r--Functions/Zftp/zfcd_match42
-rw-r--r--Functions/Zftp/zfcget87
-rw-r--r--Functions/Zftp/zfclose3
-rw-r--r--Functions/Zftp/zfcput76
-rw-r--r--Functions/Zftp/zfdir99
-rw-r--r--Functions/Zftp/zfgcp83
-rw-r--r--Functions/Zftp/zfget64
-rw-r--r--Functions/Zftp/zfget_match27
-rw-r--r--Functions/Zftp/zfhere5
-rw-r--r--Functions/Zftp/zfinit28
-rw-r--r--Functions/Zftp/zfls13
-rw-r--r--Functions/Zftp/zfopen42
-rw-r--r--Functions/Zftp/zfparams12
-rw-r--r--Functions/Zftp/zfpcp47
-rw-r--r--Functions/Zftp/zfput23
-rw-r--r--Functions/Zftp/zfrglob70
-rw-r--r--Functions/Zftp/zfrtime45
-rw-r--r--Functions/Zftp/zfstat89
-rw-r--r--Functions/Zftp/zftp_chpwd39
-rw-r--r--Functions/Zftp/zftp_progress18
-rw-r--r--Functions/Zftp/zftype30
-rw-r--r--Functions/Zftp/zfuget147
-rw-r--r--Functions/Zftp/zfuput115
27 files changed, 1363 insertions, 0 deletions
diff --git a/Functions/Zftp/README b/Functions/Zftp/README
new file mode 100644
index 000000000..794bff292
--- /dev/null
+++ b/Functions/Zftp/README
@@ -0,0 +1,4 @@
+This directory contains a set of functions acting as a front end to the
+zftp command, provided as an add-on module. They allow you to perform FTP
+tasks from within the shell using as many of the shell's own facilities
+as possible. For more information, see the zshzftpsys manual page.
diff --git a/Functions/Zftp/zfanon b/Functions/Zftp/zfanon
new file mode 100644
index 000000000..d8a9d06a3
--- /dev/null
+++ b/Functions/Zftp/zfanon
@@ -0,0 +1,70 @@
+# function zfanon {
+
+emulate -L zsh
+
+local opt optlist once
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ 1) once=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+if [[ -z $EMAIL_ADDR ]]; then
+ # Exercise in futility. There's a poem by Wallace Stevens
+ # called something like `N ways of looking at a blackbird',
+ # where N is somewhere around 0x14 to 0x18. Now zftp is
+ # ashamed to prsent `N ways of looking at a hostname'.
+ local domain host
+ # First, maybe we've already got it. Zen-like.
+ if [[ $HOST = *.* ]]; then
+ # assume this is the full host name
+ host=$HOST
+ elif [[ -f /etc/resolv.conf ]]; then
+ # Next, maybe we've got resolv.conf.
+ domain=$(awk '/domain/ { print $2 }' /etc/resolv.conf)
+ [[ -n $domain ]] && host=$HOST.$domain
+ fi
+ # Next, maybe we've got nlsookup. May not work on LINUX.
+ [[ -z $host ]] && host=$(nslookup $HOST | awk '/Name:/ { print $2 }')
+ if [[ -z $host ]]; then
+ # we're running out of ideas, but this should work.
+ # after all, i wrote it...
+ # don't want user to know about this, too embarrassed.
+ local oldvb=$ZFTP_VERBOSE oldtm=$ZFTP_TMOUT
+ ZFTP_VERBOSE=
+ ZFTP_TMOUT=5
+ if zftp open $host >& /dev/null; then
+ host=$ZFTP_HOST
+ zftp close $host
+ fi
+ ZFTP_VERBOSE=$oldvb
+ ZFTP_TMOUT=$oldtm
+ fi
+ if [[ -z $host ]]; then
+ print "Can't get your hostname. Define \$EMAIL_ADDR by hand."
+ return 1;
+ fi
+ EMAIL_ADDR="$USER@$host"
+ print "Using $EMAIL_ADDR as anonymous FTP password."
+fi
+
+if [[ $once = 1 ]]; then
+ zftp open $1 anonymous $EMAIL_ADDR
+else
+ zftp params $1 anonymous $EMAIL_ADDR
+ zftp open
+fi
+# }
diff --git a/Functions/Zftp/zfautocheck b/Functions/Zftp/zfautocheck
new file mode 100644
index 000000000..abb994061
--- /dev/null
+++ b/Functions/Zftp/zfautocheck
@@ -0,0 +1,33 @@
+# function zfautocheck {
+# This function is used to implement auto-open behaviour.
+#
+# With first argument including n, don't change to the old directory; else do.
+#
+# Set do_close to 1 if the connection was not previously open, 0 otherwise
+# With first arguemnt including d, don't set do_close to 1. Broadly
+# speaking, we use this mechanism to shut the connection after use
+# if the connection had been explicitly closed (i.e. didn't time out,
+# which zftp test investigates) and we are not using a directory
+# command, which implies we are looking for something so should stay open
+# for it.
+
+# Remember the old session: zflastsession will be overwritten by
+# a successful open.
+local lastsession=$zflastsession
+
+if [[ -z $ZFTP_HOST ]]; then
+ zfopen || return 1
+ [[ $1 = *d* ]] || do_close=1
+elif zftp test 2>/dev/null; then
+ return 0
+else
+ zfopen || return 1
+fi
+
+if [[ $1 = *n* ]]; then
+ return 0
+elif [[ -n $lastsession && $ZFTP_HOST = ${lastsession%%:*} ]]; then
+ zfcd ${lastsession#*:}
+fi
+
+# }
diff --git a/Functions/Zftp/zfcd b/Functions/Zftp/zfcd
new file mode 100644
index 000000000..b726d9f55
--- /dev/null
+++ b/Functions/Zftp/zfcd
@@ -0,0 +1,52 @@
+# function zfcd {
+# zfcd: change directory on the remote server.
+#
+# Currently has the following features:
+# --- an initial string matching $HOME in the directory is turned back into ~
+# to be re-interpreted by the remote server.
+# --- zfcd with no arguments changes directory to '~'
+# --- `zfcd old new' and `zfcd -' work analagously to cd
+# --- if the connection is not currently open, it will try to
+# re-open it with the stored parameters as set by zfopen.
+# If the connection timed out, however, it won't know until
+# too late. In that case, just try the same zfcd command again
+# (but now `zfcd -' and `zfcd old new' won't work).
+
+# hack: if directory begins with $HOME, turn it back into ~
+# there are two reasons for this:
+# first, a ~ on the command line gets expanded even with noglob.
+# (I suppose this is correct, but I wouldn't like to swear to it.)
+# second, we can no do 'zfcd $PWD' and the like, and that will
+# work just as long as the directory structures under the home match.
+
+emulate -L zsh
+
+if [[ $1 = /* ]]; then
+ zfautocheck -dn
+else
+ zfautocheck -d
+fi
+
+if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+fi
+
+if (( $# == 0 )); then
+ # Emulate `cd' behaviour
+ set -- '~'
+elif [[ $# -eq 1 && $1 = - ]]; then
+ # Emulate `cd -' behaviour.
+ set -- $zflastdir
+elif [[ $# -eq 2 ]]; then
+ # Emulate `cd old new' behaviour.
+ # We have to find a character not in $1 or $2; ! is a good bet.
+ eval set -- "\${ZFTP_PWD:s!$1!$2!}"
+fi
+
+# We have to remember the current directory before changing it
+# if we want to keep it.
+local lastdir=$ZFTP_PWD
+
+zftp cd "$@" && zflastdir=$lastdir
+print $zflastsession
+# }
diff --git a/Functions/Zftp/zfcd_match b/Functions/Zftp/zfcd_match
new file mode 100644
index 000000000..67e719888
--- /dev/null
+++ b/Functions/Zftp/zfcd_match
@@ -0,0 +1,42 @@
+# function zfcd_match {
+
+emulate -L zsh
+
+# see zfcd for details of this hack
+if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+fi
+
+# error messages only
+local ZFTP_VERBOSE=45
+# should we redirect 2>/dev/null or let the user see it?
+
+local tmpf=${TMPPREFIX}zfcm$$
+
+if [[ $ZFTP_SYSTEM = UNIX* ]]; then
+ # hoo, aren't we lucky: this makes things so much easier
+ setopt localoptions rcexpandparam
+ local dir
+ if [[ $1 = ?*/* ]]; then
+ dir=${1%/*}
+ elif [[ $1 = /* ]]; then
+ dir=/
+ fi
+ # If we're using -F, we get away with using a directory
+ # to list, but not a glob. Don't ask me why.
+ # I hate having to rely on awk here.
+ zftp ls -F $dir >$tmpf
+ reply=($(awk '/\/$/ { print substr($1, 0, length($1)-1) }' $tmpf))
+ rm -f $tmpf
+ if [[ $dir = / ]]; then
+ reply=(${dir}$reply)
+ elif [[ -n $dir ]]; then
+ reply=($dir/$reply)
+ fi
+else
+ # I simply don't know what to do here.
+ # Just use the list of files for the current directory.
+ zfget_match $*
+fi
+
+# }
diff --git a/Functions/Zftp/zfcget b/Functions/Zftp/zfcget
new file mode 100644
index 000000000..fd6accfed
--- /dev/null
+++ b/Functions/Zftp/zfcget
@@ -0,0 +1,87 @@
+# function zfcget {
+# Continuation get of files from remote server.
+# For each file, if it's shorter here, try to get the remainder from
+# over there. This requires the server to support the REST command
+# in the way many do but RFC959 doesn't specify.
+# Options:
+# -G don't to remote globbing, else do
+# -t update the local file times to the same time as the remote.
+# Currently this only works if you have the `perl' command,
+# and that perl is version 5 with the standard library.
+# See the function zfrtime for more gory details.
+
+emulate -L zsh
+
+local loc rem stat=0 optlist opt nglob remlist locst remst
+local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize time
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ if [[ ! -f $loc ]]; then
+ # File does not yet exist
+ zftp get $rem >$loc || stat=$?
+ else
+ # Compare the sizes.
+ locst=($(zftp local $loc))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remst=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not support SIZE command.\n" \
+ "Assuming you know what you're doing..." 2>&1
+ zftp getat $rem $locst[1] >>$loc || stat=$?
+ continue
+ elif [[ $rstat = 1 ]]; then
+ print "Remote file not found: $rem" 2>&1
+ continue
+ fi
+ if [[ $locst[1] -gt $remst[1] ]]; then
+ print "Local file is larger!" 2>&1
+ continue;
+ elif [[ $locst[1] == $remst[1] ]]; then
+ print "Files are already the same size." 2>&1
+ continue
+ else
+ if zftp getat $rem $locst[1] >>$loc; then
+ [[ $time = 1 ]] && zfrtime $loc $rem $remst[2]
+ else
+ stat=1
+ fi
+ fi
+ fi
+ done
+ fi
+done
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfclose b/Functions/Zftp/zfclose
new file mode 100644
index 000000000..fb49efd51
--- /dev/null
+++ b/Functions/Zftp/zfclose
@@ -0,0 +1,3 @@
+# function zfclose {
+zftp close
+# }
diff --git a/Functions/Zftp/zfcput b/Functions/Zftp/zfcput
new file mode 100644
index 000000000..fad5c3f86
--- /dev/null
+++ b/Functions/Zftp/zfcput
@@ -0,0 +1,76 @@
+# function zfcput {
+# Continuation put of files from remote server.
+# For each file, if it's shorter over there, put the remainder from
+# over here. This uses append, which is standard, so unlike zfcget it's
+# expected to work on any reasonable server... err, as long as it
+# supports SIZE and MDTM. (It could be enhanced so you can enter the
+# size so far by hand.) You should probably be in binary transfer
+# mode, thought it's not enforced.
+#
+# To read from midway through a local file, `tail +<n>c' is used.
+# It would be nice to find a way of doing this which works on all OS's.
+
+emulate -L zsh
+
+local loc rem stat=0 locst remst offs tailtype
+local tmpfile=${TMPPREFIX}zfcget$$ rstat
+
+# find how tail works. this is intensely annoying, since it's completely
+# standard in C. od's no use, since we can only skip whole blocks.
+if [[ $(echo abcd | tail +2c) = bcd ]]; then
+ tailtype=c
+elif [[ $(echo abcd | tail --bytes=+2) = bcd ]]; then
+ tailtype=b
+else
+ print "I can't get your \`tail' to start from from arbitrary characters.\n" \
+ "If you know how to do this, let me know." 2>&1
+ return 1
+fi
+
+for loc in $*; do
+ # zfcd directory hack to put the front back to ~
+ rem=$loc
+ if [[ $rem = $HOME || $rem = $HOME/* ]]; then
+ rem="~${rem#$HOME}"
+ fi
+ if [[ ! -r $loc ]]; then
+ print "Can't read file $loc"
+ stat=1
+ else
+ # Compare the sizes.
+ locst=($(zftp local $loc))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remst=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not support remote status commands.\n" \
+ "You will have to find out the size by hand and use zftp append." 2>&1
+ stat=1
+ continue
+ elif [[ $rstat = 1 ]]; then
+ # Not found, so just do a standard put.
+ zftp put $rem <$loc
+ elif [[ $remst[1] -gt $locst[1] ]]; then
+ print "Remote file is larger!" 2>&1
+ continue;
+ elif [[ $locst[1] == $remst[1] ]]; then
+ print "Files are already the same size." 2>&1
+ continue
+ else
+ # tail +<N>c takes the count of the character
+ # to start from, not the offset from zero. if we did
+ # this with years, then 2000 would be 1999. no y2k bug!
+ # brilliant.
+ (( offs = $remst[1] + 1 ))
+ if [[ $tailtype = c ]]; then
+ tail +${offs}c $loc | zftp append $rem || stat=1
+ else
+ tail --bytes=+$offs $loc | zftp append $rem || stat=1
+ fi
+ fi
+ fi
+done
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfdir b/Functions/Zftp/zfdir
new file mode 100644
index 000000000..55befe000
--- /dev/null
+++ b/Functions/Zftp/zfdir
@@ -0,0 +1,99 @@
+# function zfdir {
+# Long directory of remote server.
+# The remote directory is cached. In fact, two caches are kept:
+# one of the standard listing of the current directory, i.e. zfdir
+# with no arguments, and another for everything else.
+# To access the appropriate cache, just use zfdir with the same
+# arguments as previously. zfdir -r will also re-use the `everything
+# else' cache; you can always reuse the current directory cache just
+# with zfdir on its own.
+#
+# The current directory cache is emptied when the directory changes;
+# the other is kept until a new zfdir with a non-empty argument list.
+# Both are removed when the connection is closed.
+#
+# zfdir -f will force the existing cache to be ignored, e.g. if you know
+# or suspect the directory has changed.
+# zfdir -d will remove both caches without listing anything.
+# If you need to pass -r, -f or -d to the dir itself, use zfdir -- -d etc.;
+# unrecognised options are passed through to dir, but zfdir options must
+# appear first and unmixed with the others.
+
+emulate -L zsh
+setopt extendedglob
+
+local file opt optlist redir i newargs force
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ elif [[ $1 != -[rfd]## ]]; then
+ # pass options through to ls
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ r) redir=1
+ ;;
+ f) force=1
+ ;;
+ d) [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+ [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+ zftp_fcache=()
+ return 0
+ ;;
+ esac
+ done
+ shift
+done
+
+zfautocheck -d
+
+# directory hack, see zfcd
+for (( i = 1; i <= $#argv; i++ )); do
+ if [[ $argv[$i] = $HOME || $argv[$i] = $HOME/* ]]; then
+ argv[$i]="~${argv[$i]#$HOME}"
+ fi
+done
+
+if [[ $# -eq 0 ]]; then
+ # Cache it in the current directory file. This means that repeated
+ # calls to zfdir with no arguments always use a cached file.
+ [[ -z $zfcurdir ]] && zfcurdir=${TMPPREFIX}zfcurdir$$
+ file=$zfcurdir
+else
+ # Last directly looked at was not the current one, or at least
+ # had non-standard arguments.
+ [[ -z $zfotherdir ]] && zfotherdir=${TMPPREFIX}zfotherdir$$
+ file=$zfotherdir
+ newargs="$*"
+ if [[ -f $file && $redir != 1 && $force -ne 1 ]]; then
+ # Don't use the cached file if the arguments changed.
+ [[ $newargs = $zfotherargs ]] || rm -f $file
+ fi
+ zfotherargs=$newargs
+fi
+
+if [[ $force -eq 1 ]]; then
+ rm -f $file
+ # if it looks like current directory has changed, better invalidate
+ # the filename cache, too.
+ (( $# == 0 )) && zftp_fcache=()
+fi
+
+if [[ -n $file && -f $file ]]; then
+ eval ${PAGER:-more} \$file
+else
+ if (zftp test); then
+ # Works OK in subshells
+ zftp dir $* | tee $file | eval ${PAGER-:more}
+ else
+ # Doesn't work in subshells (IRIX 6.2 --- why?)
+ zftp dir $* >$file
+ eval ${PAGER-:more} $file
+ fi
+fi
+# }
diff --git a/Functions/Zftp/zfgcp b/Functions/Zftp/zfgcp
new file mode 100644
index 000000000..26a08697d
--- /dev/null
+++ b/Functions/Zftp/zfgcp
@@ -0,0 +1,83 @@
+# function zfgcp {
+# ZFTP get as copy: i.e. first arguments are remote, last is local.
+# Supposed to work exactly like a normal copy otherwise, i.e.
+# zfgcp rfile lfile
+# or
+# zfgcp rfile1 rfile2 rfile3 ... ldir
+# Options:
+# -G don't to remote globbing, else do
+# -t update the local file times to the same time as the remote.
+# Currently this only works if you have the `perl' command,
+# and that perl is version 5 with the standard library.
+# See the function zfrtime for more gory details.
+#
+# If there is no current connection, try to use the existing set of open
+# parameters to establish one and close it immediately afterwards.
+
+emulate -L zsh
+
+local opt optlist nglob remlist rem loc time
+integer stat do_close
+
+while [[ $1 == -* ]]; do
+ if [[ $1 == - || $1 == -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+zfautocheck
+
+# hmm, we should really check this after expanding the glob,
+# but we shouldn't expand the last argument remotely anyway.
+if [[ $# -gt 2 && ! -d $argv[-1] ]]; then
+ print "zfgcp: last argument must be a directory." 2>&1
+ return 1
+elif [[ $# == 1 ]]; then
+ print "zfgcp: not enough arguments." 2>&1
+ return 1
+fi
+
+if [[ -d $argv[-1] ]]; then
+ local dir=$argv[-1]
+ argv[-1]=
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=$dir/${rem:t}
+ if zftp get $rem >$loc; then
+ [[ $time = 1 ]] && zfrtime $rem $loc
+ else
+ stat=1
+ fi
+ done
+ fi
+ done
+else
+ zftp get $1 >$2 || stat=$?
+fi
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfget b/Functions/Zftp/zfget
new file mode 100644
index 000000000..878a36346
--- /dev/null
+++ b/Functions/Zftp/zfget
@@ -0,0 +1,64 @@
+# function zfget {
+# Get files from remote server. Options:
+# -G don't to remote globbing, else do
+# -t update the local file times to the same time as the remote.
+# Currently this only works if you have the `perl' command,
+# and that perl is version 5 with the standard library.
+# See the function zfrtime for more gory details.
+#
+# If the connection is not currently open, try to open it with the current
+# parameters (set by a previous zfopen or zfparams), then close it after
+# use. The file is put in the current directory (i.e. using the basename
+# of the remote file only); for more control, use zfgcp.
+
+emulate -L zsh
+
+local loc rem optlist opt nglob remlist time
+integer stat do_close
+
+while [[ $1 == -* ]]; do
+ if [[ $1 == - || $1 == -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+zfautocheck
+
+for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ if zftp get $rem >$loc; then
+ [[ $time = 1 ]] && zfrtime $rem $loc
+ else
+ stat=1
+ fi
+ done
+ fi
+done
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfget_match b/Functions/Zftp/zfget_match
new file mode 100644
index 000000000..677108ede
--- /dev/null
+++ b/Functions/Zftp/zfget_match
@@ -0,0 +1,27 @@
+# function zfget_match {
+
+emulate -L zsh
+
+# the zfcd hack: this may not be necessary here
+if [[ $1 == $HOME || $1 == $HOME/* ]]; then
+ 1="~${1#$HOME}"
+fi
+
+local tmpf=${TMPPREFIX}zfgm$$
+
+if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then
+ # On the first argument to ls, we usually get away with a glob.
+ zftp ls "$1*$2" >$tmpf
+ reply=($(<$tmpf))
+ rm -f $tmpf
+else
+ if (( $#zftp_fcache == 0 )); then
+ # Always cache the current directory and use it
+ # even if the system is UNIX.
+ zftp ls >$tmpf
+ zftp_fcache=($(<$tmpf))
+ rm -f $tmpf
+ fi
+ reply=($zftp_fcache);
+fi
+# }
diff --git a/Functions/Zftp/zfhere b/Functions/Zftp/zfhere
new file mode 100644
index 000000000..43e599d3a
--- /dev/null
+++ b/Functions/Zftp/zfhere
@@ -0,0 +1,5 @@
+# function zfhere {
+# Change to the directory corresponding to $PWD on the server.
+# See zfcd for how this works.
+zfcd $PWD
+# }
diff --git a/Functions/Zftp/zfinit b/Functions/Zftp/zfinit
new file mode 100644
index 000000000..be827c6ac
--- /dev/null
+++ b/Functions/Zftp/zfinit
@@ -0,0 +1,28 @@
+[[ $1 = -n ]] || zmodload -ia zftp
+
+alias zfcd='noglob zfcd'
+alias zfget='noglob zfget'
+alias zfls='noglob zfls'
+alias zfdir='noglob zfdir'
+alias zfuget='noglob zfuget'
+
+# only way of getting that noglob out of the way: this is unnecessary with
+# widget-based completion and can be commented out.
+setopt completealiases
+
+#
+# zftp completions: only use these if new-style completion is not
+# active.
+#
+if [[ ${#patcomps} -eq 0 || ${patcomps[(i)zf*]} -gt ${#patcomps} ]]; then
+ compctl -f -x 'p[1]' \
+ -k '(open params user login type ascii binary mode put putat
+ get getat append appendat ls dir local remote mkdir rmdir delete
+ close quit)' - \
+ 'w[1,cd][1,ls][1,dir][1,rmdir]' -K zfcd_match -S/ -q - \
+ 'W[1,get*]' -K zfget_match - 'w[1,delete][1,remote]' -K zfget_match - \
+ 'w[1,open][1,params]' -k hosts -- zftp
+ compctl -K zfcd_match -S/ -q zfcd zfdir zfls
+ compctl -K zfget_match zfget zfgcp zfuget zfcget
+ compctl -k hosts zfanon zfopen zfparams
+fi
diff --git a/Functions/Zftp/zfls b/Functions/Zftp/zfls
new file mode 100644
index 000000000..e8d3cfb28
--- /dev/null
+++ b/Functions/Zftp/zfls
@@ -0,0 +1,13 @@
+# function zfls {
+
+emulate -L zsh
+
+# directory hack, see zfcd
+if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+fi
+
+zfautocheck -d
+
+zftp ls $*
+# }
diff --git a/Functions/Zftp/zfopen b/Functions/Zftp/zfopen
new file mode 100644
index 000000000..fa9b4f81d
--- /dev/null
+++ b/Functions/Zftp/zfopen
@@ -0,0 +1,42 @@
+# function zfopen {
+# Use zftp params to set parameters for open, rather than sending
+# them straight to open. That way they are stored for a future open
+# command.
+#
+# With option -1 (just this 1ce), don't do that.
+
+emulate -L zsh
+
+local optlist opt once
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ 1) once=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+# This is where we should try and do same name-lookupage in
+# both .netrc and .ncftp/bookmarks . We could even try saving
+# the info in their for new hosts, like ncftp does.
+
+if [[ $once = 1 ]]; then
+ zftp open $*
+else
+ # set parameters, but only if there was at least a host
+ (( $# > 0 )) && zfparams $*
+ # now call with no parameters
+ zftp open
+fi
+# }
diff --git a/Functions/Zftp/zfparams b/Functions/Zftp/zfparams
new file mode 100644
index 000000000..5c5262c52
--- /dev/null
+++ b/Functions/Zftp/zfparams
@@ -0,0 +1,12 @@
+# function zfparams {
+
+emulate -L zsh
+
+# Set to prompt for any user or password if not given.
+# Don't worry about accounts here.
+if (( $# > 0 )); then
+ (( $# < 2 )) && 2='?'
+ (( $# < 3 )) && 3='?'
+fi
+zftp params $*
+# }
diff --git a/Functions/Zftp/zfpcp b/Functions/Zftp/zfpcp
new file mode 100644
index 000000000..ddd570e59
--- /dev/null
+++ b/Functions/Zftp/zfpcp
@@ -0,0 +1,47 @@
+# function zfpcp {
+# ZFTP put as copy: i.e. first arguments are remote, last is local.
+# Currently only supports
+# zfcp lfile rfile
+# if there are two arguments, or the second one is . or .., or ends
+# with a slash
+# or
+# zfcp lfile1 lfile2 lfile3 ... rdir
+# if there are more than two (because otherwise it doesn't
+# know if the last argument is a directory on the remote machine).
+# However, if the remote machine plays ball by telling us `Is a directory'
+# when we try to copy to a directory, zfpcp will then try to do the correct
+# thing.
+
+emulate -L zsh
+
+local rem loc
+integer stat do_close
+
+zfautocheck
+
+if [[ $# -gt 2 || $2 = (.|..) || $2 = */ ]]; then
+ local dir=$argv[-1]
+ argv[-1]=
+ # zfcd directory hack to put the front back to ~
+ if [[ $dir = $HOME || $dir = $HOME/* ]]; then
+ dir="~${dir#$HOME}"
+ fi
+ [[ -n $dir && $dir != */ ]] || dir="$dir/"
+ for loc in $*; do
+ rem=$dir${loc:t}
+ zftp put $rem <$loc || stat=1
+ done
+else
+ zftp put $2 <$1
+ stat=$?
+ if [[ stat -ne 0 && $ZFTP_CODE = 553 && $ZFTP_REPLY = *'Is a directory'* ]]
+ then
+ zftp put $2/$1:t <$1
+ stat=$?
+ fi
+fi
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfput b/Functions/Zftp/zfput
new file mode 100644
index 000000000..0687163f0
--- /dev/null
+++ b/Functions/Zftp/zfput
@@ -0,0 +1,23 @@
+# function zfput {
+# Simple put: dump every file under the same name, but stripping
+# off any directory parts to get the remote filename (i.e. always
+# goes into current remote directory). Use zfpcp to specify new
+# file name or new directory at remote end.
+
+emulate -L zsh
+
+local loc rem
+integer stat do_close
+
+zfautocheck
+
+for loc in $*; do
+ rem=${loc:t}
+ zftp put $rem <$loc
+ [[ $? == 0 ]] || stat=$?
+done
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfrglob b/Functions/Zftp/zfrglob
new file mode 100644
index 000000000..f9d67b3f2
--- /dev/null
+++ b/Functions/Zftp/zfrglob
@@ -0,0 +1,70 @@
+# function zfrglob {
+# Do the remote globbing for zfput, etc.
+# We have two choices:
+# (1) Get the entire file list and match it one by one
+# locally against the pattern.
+# Causes problems if we are globbing directories (rare, presumably).
+# But: we can cache the current directory, which
+# we need for completion anyway. Works on any OS if you
+# stick with a single directory. This is the default.
+# (2) Use remote globbing, i.e. pass it to ls at the site.
+# Faster, but only works with UNIX, and only basic globbing.
+# We do this if $zfrglob is non-null.
+
+# There is only one argument, the variable containing the
+# pattern to be globbed. We set this back to an array containing
+# all the matches.
+
+emulate -L zsh
+setopt extendedglob
+
+local pat dir nondir files i
+
+eval pat=\$$1
+
+# Check if we really need to do anything. Look for standard
+# globbing characters, and if we are
+# using zsh for the actual pattern matching also look for
+# extendedglob characters.
+if [[ $pat != *[][*?]* &&
+ ( -n $zfrglob || $pat != *[(|)#^]* ) ]]; then
+ return 0
+fi
+local tmpf=${TMPPREFIX}zfrglob$$
+
+if [[ $zfrglob != '' ]]; then
+ zftp ls "$pat" >$tmpf 2>/dev/null
+ eval "$1=(\$(<\$tmpf))"
+ rm -f $tmpf
+else
+ if [[ $ZFTP_SYSTEM = UNIX* && $pat = */* ]]; then
+ # not the current directory and we know how to handle paths
+ if [[ $pat = ?*/* ]]; then
+ # careful not to remove too many slashes
+ dir=${pat%/*}
+ else
+ dir=/
+ fi
+ nondir=${pat##*/}
+ zftp ls "$dir" 2>/dev/null >$tmpf
+ files=($(<$tmpf))
+ files=(${files:t})
+ rm -f $tmpf
+ else
+ # we just have to do an ls and hope that's right
+ nondir=$pat
+ if (( $#zftp_fcache == 0 )); then
+ # Why does `zftp_fcache=($(zftp ls))' sometimes not work?
+ zftp ls >$tmpf
+ zftp_fcache=($(<$tmpf))
+ rm -f $tmpf
+ fi
+ files=($zftp_fcache)
+ fi
+ # now we want to see which of the $files match $nondir:
+ # ${...:/foo} deletes occurrences of foo matching a complete word,
+ # while the ^ inverts the sense so that anything not matching the
+ # pattern in $nondir is excluded.
+ eval "$1=(\${files:/^\${~nondir}})"
+fi
+# }
diff --git a/Functions/Zftp/zfrtime b/Functions/Zftp/zfrtime
new file mode 100644
index 000000000..f63ffe04b
--- /dev/null
+++ b/Functions/Zftp/zfrtime
@@ -0,0 +1,45 @@
+# function zfrtime {
+# Set the modification time of file LOCAL to that of REMOTE.
+# If the optional TIME is passed, it should be in the FTP format
+# CCYYMMDDhhmmSS, i.e. no dot before the seconds, and in GMT.
+# This is what both `zftp remote' and `zftp local' return.
+#
+# Unfortunately, since the time returned from FTP is GMT and
+# your file needs to be set in local time, we need to do some
+# hacking around with time. At the moment this requires perl 5
+# with the standard library.
+
+emulate -L zsh
+
+local time gmtime loctime
+
+if [[ -n $3 ]]; then
+ time=$3
+else
+ time=($(zftp remote $2 2>/dev/null))
+ [[ -n $time ]] && time=$time[2]
+fi
+[[ -z $time ]] && return 1
+
+# Now's the real *!@**!?!. We have the date in GMT and want to turn
+# it into local time for touch to handle. It's just too nasty
+# to handle in zsh; do it in perl.
+if perl -mTime::Local -e '($file, $t) = @ARGV;
+$yr = substr($t, 0, 4) - 1900;
+$mon = substr($t, 4, 2) - 1;
+$mday = substr($t, 6, 2) + 0;
+$hr = substr($t, 8, 2) + 0;
+$min = substr($t, 10, 2) + 0;
+$sec = substr($t, 12, 2) + 0;
+$time = Time::Local::timegm($sec, $min, $hr, $mday, $mon, $yr);
+utime $time, $time, $file and return 0;' $1 $time 2>/dev/null; then
+ print "Setting time for $1 failed. Need perl 5." 2>1
+fi
+
+# If it wasn't for the GMT/local time thing, it would be this simple.
+#
+# time="${time[1,12]}.${time[13,14]}"
+#
+# touch -t $time $1
+
+# }
diff --git a/Functions/Zftp/zfstat b/Functions/Zftp/zfstat
new file mode 100644
index 000000000..0ca755d03
--- /dev/null
+++ b/Functions/Zftp/zfstat
@@ -0,0 +1,89 @@
+# function zfstat {
+# Give a zftp status report using local variables.
+# With option -v, connect to the remote host and ask it what it
+# thinks the status is.
+
+setopt localoptions unset
+unsetopt ksharrays
+
+local i stat=0 opt optlist verbose
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ v) verbose=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+if [[ -n $ZFTP_HOST ]]; then
+ print "Host:\t\t$ZFTP_HOST"
+ print "IP:\t\t$ZFTP_IP"
+ [[ -n $ZFTP_SYSTEM ]] && print "System type:\t$ZFTP_SYSTEM"
+ if [[ -n $ZFTP_USER ]]; then
+ print "User:\t\t$ZFTP_USER "
+ [[ -n $ZFTP_ACCOUNT ]] && print "Account:\t$AFTP_ACCOUNT"
+ print "Directory:\t$ZFTP_PWD"
+ print -n "Transfer type:\t"
+ if [[ $ZFTP_TYPE = "I" ]]; then
+ print Image
+ elif [[ $ZFTP_TYPE = "A" ]]; then
+ print Ascii
+ else
+ print Unknown
+ fi
+ print -n "Transfer mode:\t"
+ if [[ $ZFTP_MODE = "S" ]]; then
+ print Stream
+ elif [[ $ZFTP_MODE = "B" ]]; then
+ print Block
+ else
+ print Unknown
+ fi
+ else
+ print "No user logged in."
+ fi
+else
+ print "Not connected."
+ [[ -n $zflastsession ]] && print "Last session:\t$zflastsession"
+ stat=1
+fi
+
+# things which may be set even if not connected:
+[[ -n $ZFTP_REPLY ]] && print "Last reply:\t$ZFTP_REPLY"
+print "Verbosity:\t$ZFTP_VERBOSE"
+print "Timeout:\t$ZFTP_TMOUT"
+print -n "Preferences:\t"
+for (( i = 1; i <= ${#ZFTP_PREFS}; i++ )); do
+ case $ZFTP_PREFS[$i] in
+ [pP]) print -n "Passive "
+ ;;
+ [sS]) print -n "Sendport "
+ ;;
+ [dD]) print -n "Dumb "
+ ;;
+ *) print -n "$ZFTP_PREFS[$i]???"
+ esac
+done
+print
+
+if [[ -n $ZFTP_HOST && $verbose = 1 ]]; then
+ zfautocheck -d
+ print "Status of remote server:"
+ # make sure we print the reply
+ local ZFTP_VERBOSE=045
+ zftp quote STAT
+fi
+
+return $stat
+# }
diff --git a/Functions/Zftp/zftp_chpwd b/Functions/Zftp/zftp_chpwd
new file mode 100644
index 000000000..0df199cfb
--- /dev/null
+++ b/Functions/Zftp/zftp_chpwd
@@ -0,0 +1,39 @@
+# function zftp_chpwd {
+# You may want to alter chpwd to call this when $ZFTP_USER is set.
+
+# Cancel the filename cache for the current directory.
+zftp_fcache=()
+# ...and also empty the stored directory listing cache.
+# As this function is called when we close the connection, this
+# is the only place we need to do these two things.
+[[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+zfotherargs=
+
+if [[ -z $ZFTP_USER ]]; then
+ # last call, after an FTP logout
+
+ # delete the non-current cached directory
+ [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+
+ # don't keep zflastdir between opens (do keep zflastsession)
+ zflastdir=
+
+ # return the display to standard
+ # uncomment the following line if you have a chpwd which shows directories
+ # chpwd
+else
+ [[ -n $ZFTP_PWD ]] && zflastdir=$ZFTP_PWD
+ zflastsession="$ZFTP_HOST:$ZFTP_PWD"
+ local args
+ if [[ -t 1 && -t 2 ]]; then
+ local str=$zflastsession
+ [[ ${#str} -lt 70 ]] && str="%m: %~ $str"
+ case $TERM in
+ sun-cmd) print -n -P "\033]l$str\033\\"
+ ;;
+ xterm) print -n -P "\033]2;$str\a"
+ ;;
+ esac
+ fi
+fi
+# }
diff --git a/Functions/Zftp/zftp_progress b/Functions/Zftp/zftp_progress
new file mode 100644
index 000000000..e2b5084c4
--- /dev/null
+++ b/Functions/Zftp/zftp_progress
@@ -0,0 +1,18 @@
+# function zftp_progress {
+# Basic progress metre, showing the percent of the file transferred.
+# You want growing bars? You gotta write growing bars.
+
+# Don't show progress unless stderr is a terminal
+[[ ! -t 2 ]] && return 0
+
+if [[ $ZFTP_TRANSFER = *F ]]; then
+ print 1>&2
+elif [[ -n $ZFTP_TRANSFER ]]; then
+ if [[ -n $ZFTP_SIZE ]]; then
+ local frac="$(( ZFTP_COUNT * 100 / ZFTP_SIZE ))%"
+ print -n "\r$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER $frac" 1>&2
+ else
+ print -n "\r$ZFTP_FILE: $ZFTP_TRANSFER $ZFTP_COUNT" 1>&2
+ fi
+fi
+# }
diff --git a/Functions/Zftp/zftype b/Functions/Zftp/zftype
new file mode 100644
index 000000000..c3b93b7a0
--- /dev/null
+++ b/Functions/Zftp/zftype
@@ -0,0 +1,30 @@
+# function zftype {
+local type zftmp=${TMPPREFIX}zftype$$
+
+zfautocheck -d
+
+if (( $# == 0 )); then
+ zftp type >$zftmp
+ type=$(<$zftmp)
+ rm -f $zftmp
+ if [[ $type = I ]]; then
+ print "Current type is image (binary)"
+ return 0
+ elif [[ $type = A ]]; then
+ print "Current type is ASCII"
+ return 0
+ else
+ return 1
+ fi
+else
+ if [[ $1 == (#i)a(sc(ii|)|) ]]; then
+ type=A
+ elif [[ $1 == (#i)i(m(age|)|) || $1 == (#i)b(in(ary|)|) ]]; then
+ type=I
+ else
+ print "Type not recognised: $1" 2>&1
+ return 1
+ fi
+ zftp type $type
+fi
+# }
diff --git a/Functions/Zftp/zfuget b/Functions/Zftp/zfuget
new file mode 100644
index 000000000..482da42e9
--- /dev/null
+++ b/Functions/Zftp/zfuget
@@ -0,0 +1,147 @@
+# function zfuget {
+# Get a list of files from the server with update.
+# In other words, only retrieve files which are newer than local
+# ones. This depends on the clocks being adjusted correctly
+# (i.e. if one is fifteen minutes out, for the next fifteen minutes
+# updates may not be correctly calculated). However, difficult
+# cases --- where the files are the same size, but the remote is newer,
+# or have different sizes, but the local is newer -- are prompted for.
+#
+# Files are globbed on the remote host --- assuming, of course, they
+# haven't already been globbed local, so use 'noglob' e.g. as
+# `alias zfuget="noglob zfuget"'.
+#
+# Options:
+# -G Glob: turn off globbing
+# -v verbose: print more about the files listed.
+# -s silent: don't ask, just guess. The guesses are:
+# - if the files have different sizes but remote is older ) grab
+# - if they have the same size but remote is newer )
+# which is safe if the remote files are always the right ones.
+# -t time: update the local file times to the same time as the remote.
+# Currently this only works if you have the `perl' command,
+# and that perl is version 5 with the standard library.
+# See the function zfrtime for more gory details.
+
+emulate -L zsh
+
+local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$
+local rstat remlist verbose optlist opt bad i silent nglob time
+integer stat do_close
+
+zfuget_print_time() {
+ local tim=$1
+ print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+ print -n GMT
+}
+
+zfuget_print () {
+ print -n "\nremote $rem ("
+ zfuget_print_time $remstats[2]
+ print -n ", $remstats[1] bytes)\nlocal $loc ("
+ zfuget_print_time $locstats[2]
+ print ", $locstats[1] bytes)"
+}
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ v) verbose=1
+ ;;
+ s) silent=1
+ ;;
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+[[ -n $bad ]] && return 1
+
+zfautocheck
+
+for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ doit=y
+ remstats=()
+ if [[ -f $loc ]]; then
+ zftp local $loc >$tmpfile
+ locstats=($(<$tmpfile))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remstats=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not implement full command set required." 1>&2
+ return 1
+ elif [[ $rstat = 1 ]]; then
+ print "File not found on server: $rem" 1>&2
+ stat=1
+ continue
+ fi
+ [[ $verbose = 1 ]] && zfuget_print
+ if (( $locstats[1] != $remstats[1] )); then
+ # Files have different sizes
+ if [[ $locstats[2] > $remstats[2] && $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuget_print
+ print "Local file $loc more recent than remote," 1>&2
+ print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # Files have same size
+ if [[ $locstats[2] < $remstats[2] ]]; then
+ if [[ $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuget_print
+ print "Local file $loc has same size as remote," 1>&2
+ print -n "but local file is older. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # presumably same file, so don't get it.
+ [[ $verbose = 1 ]] && print Not transferring
+ doit=n
+ fi
+ fi
+ else
+ [[ $verbose = 1 ]] && print New file $loc
+ fi
+ if [[ $doit = y ]]; then
+ if zftp get $rem >$loc; then
+ if [[ $time = 1 ]]; then
+ # if $remstats is set, it's second element is the remote time
+ zfrtime $loc $rem $remstats[2]
+ fi
+ else
+ stat=$?
+ fi
+
+ fi
+ done
+ fi
+done
+
+(( do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfuput b/Functions/Zftp/zfuput
new file mode 100644
index 000000000..b54d0d0d4
--- /dev/null
+++ b/Functions/Zftp/zfuput
@@ -0,0 +1,115 @@
+# function zfuput {
+# Put a list of files from the server with update.
+# See zfuget for details.
+#
+# Options:
+# -v verbose: print more about the files listed.
+# -s silent: don't ask, just guess. The guesses are:
+# - if the files have different sizes but remote is older ) grab
+# - if they have the same size but remote is newer )
+# which is safe if the remote files are always the right ones.
+
+emulate -L zsh
+
+local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$
+local rstat verbose optlist opt bad i silent
+integer stat do_close
+
+zfuput_print_time() {
+ local tim=$1
+ print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+ print -n GMT
+}
+
+zfuput_print () {
+ print -n "\nremote $rem ("
+ zfuput_print_time $remstats[2]
+ print -n ", $remstats[1] bytes)\nlocal $loc ("
+ zfuput_print_time $locstats[2]
+ print ", $locstats[1] bytes)"
+}
+
+while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ v) verbose=1
+ ;;
+ s) silent=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+done
+
+[[ -n $bad ]] && return 1
+
+zfautocheck
+
+if [[ $ZFTP_VERBOSE = *5* ]]; then
+ # should we turn it off locally?
+ print "Messages with code 550 are harmless." >&2
+fi
+
+for rem in $*; do
+ loc=${rem:t}
+ doit=y
+ remstats=()
+ if [[ ! -f $loc ]]; then
+ print "$loc: file not found" >&2
+ stat=1
+ continue
+ fi
+ zftp local $loc >$tmpfile
+ locstats=($(<$tmpfile))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remstats=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not implement full command set required." 1>&2
+ return 1
+ elif [[ $rstat = 1 ]]; then
+ [[ $verbose = 1 ]] && print New file $loc
+ else
+ [[ $verbose = 1 ]] && zfuput_print
+ if (( $locstats[1] != $remstats[1] )); then
+ # Files have different sizes
+ if [[ $locstats[2] < $remstats[2] && $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuput_print
+ print "Remote file $rem more recent than local," 1>&2
+ print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # Files have same size
+ if [[ $locstats[2] > $remstats[2] ]]; then
+ if [[ $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuput_print
+ print "Remote file $rem has same size as local," 1>&2
+ print -n "but remote file is older. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # presumably same file, so don't get it.
+ [[ $verbose = 1 ]] && print Not transferring
+ doit=n
+ fi
+ fi
+ fi
+ if [[ $doit = y ]]; then
+ zftp put $rem <$loc || stat=$?
+ fi
+done
+
+(( do_close )) && zfclose
+
+return $stat
+# }