#!/bin/sh
me=$(basename $0)
usage() {
err=$1; out=2
[ $err -eq 0 ] && out=1
printf "\
$me [OPTION] 'FILTER' FILE...
Replace the contents of each FILE after processing with FILTER
--atomic at no point leave a FILE unavailable or inconsistent
--backup[=CONTROL] make a backup of each existing destination file
-b like --backup but does not accept an argument
-C, --compare compare each pair of source and destination files, and
in some cases, do not modify the destination at all.
-p, --preserve-timestamps apply access/modification times of SOURCE files
to corresponding destination files.
-S, --suffix=SUFFIX override the usual backup suffix
The backup suffix is \`~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.
The version control method may be selected via the --backup option or through
the VERSION_CONTROL environment variable. Here are the values:
none never make backups (even if --backup is given)
numbered make numbered backups
existing numbered if numbered backups exist, simple otherwise
simple always make simple backups
Report "$me" bugs to address@hidden
GNU coreutils home page:
General help using GNU software:
Report "$me" translation bugs to
" >&$out
exit $err
}
version() {
Cmd=$1; Date=2010; Version=8.5 #TODO: auto update
#TODO: translation
printf "\
$Cmd (GNU coreutils) $Version
Copyright (C) $Date Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later .
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Pádraig Brady.
"
exit 0
}
if getopt -T >/dev/null 2>&1; test $? -ne 4; then
# Enhanced `getopt` not available and POSIX `getopts`
# only supports short options, so revert to simple manual parsing
if test $# -lt 2; then
test "$1" = "--help" && usage 0
test "$1" = "--version" && version "$me"
usage 1
fi
else
OPT=$(
getopt -o bCpS: \
--long atomic,backup::,compare,help,\
preserve-timestamps,suffix:,version \
-n"$me" -- "$@" ||
usage 1
)
eval set -- "$OPT"
while true; do
case "$1" in
--atomic)
atomic=1; shift 1;;
-b)
test "$VERSION_CONTROL" || VERSION_CONTROL="existing"
backup="$VERSION_CONTROL"; shift;;
--backup)
case "$2" in
"") backup=existing; shift 2;;
*) backup="$2"; shift 2;;
esac;;
-C|--compare) compare=1; shift;;
-p|--preserve-timestamps) preserve_times=1; shift;;
-S|--suffix) suffix="$2"; shift 2;;
--help) usage 0; shift;;
--version) version "$me"; shift;;
--) shift; break ;;
*) printf "%s\n" "Option processing error" >&2; exit 1;;
esac
done
if test "$backup"; then
backup="--backup=$backup"
test "$suffix" && backup="$backup --suffix=$suffix"
fi
if test $# -lt 1; then
printf "\
$me: missing file operand
Try \`$me --help' for more information.
"
fi
fi
filter="$1"; shift
cleanup() { rm -f "$tf"; }
trap "cleanup" EXIT
filter_file() {
ret=0
if $filter < "$file" > "$tf"; then
if test "$preserve_times"; then
touch "$tf" --reference="$file" || { ret=1; fail=1; }
fi
else
ret=1; fail=1
fi
if test $ret = 0; then
if test "$compare"; then
cmp -s -- "$tf" "$file"
status=$?
test $status -eq 0 && { ret=1; } # Don't process further
test $status -gt 1 && { ret=1; fail=1; }
fi
fi
return $ret
}
# Revert to a slower way of copying attributes if the fast way is unavailable
attr="--attributes-only"
cp --attributes-only --version >/dev/null 2>&1 || attr="-a"
fail=0
for file in "$@"; do
dir=$(dirname -- "$file")
cleanup
tf=$(mktemp -q --tmpdir="$dir")
#XXX: Need to cleanup always?
backup_err=0
if test -e "$tf" && cp $attr -- "$file" "$tf" 2>/dev/null; then
# Modify file atomically.
# Note if we passed $backup to `mv` then the data will be
# atomically consistent but the file may be missing for a short period.
# Therefore we make an explicit backup first.
# Note `sed -i.bak` uses the quick rename() method, thus having the issue.
# Note sed/mv could use this hardlink method to implement backups?
# XXX: should try the first hardlink in the non atomic enforcing case also?
if test "$backup" && test "$atomic"; then
mv_backup=""
bak=$(cp $backup -vf -l "$file" "$file" | sed "s/.* -> \`\(.*\)'/\1/") ||
bak=$(cp $backup -vf -a "$file" "$file" | sed "s/.* -> \`\(.*\)'/\1/") ||
{ backup_err=1; fail=1; rm -f "$bak"; }
else
mv_backup="$backup"
fi
# We could (prompt to) `chmod u+rw` here to allow updating non rw files?
if ! test -w "$file"; then
# This clause is only so we present a better error message when
# the file is readonly, as then the error is reported against "$tf"
rm -f "$bak"
printf "%s\n" "$me: $file: Permission denied" >&2
fail=1
else
test $backup_err=0 && filter_file &&
{ mv $mv_backup -- "$tf" "$file" || fail=1; } || # rename
rm -f "$bak"
fi
elif test "$atomic"; then # repeat to output errors
cleanup
tf=$(mktemp --tmpdir="$dir") &&
cp $attr -- "$file" "$tf"
else
# $dir may not be writeable. In this case we
# use $TMPDIR, but don't use mv to unlink/copy
# as $TMPDIR might not support all attrs of $dir.
# Also we can't unlink in unwriteable dir.
# We could (prompt to) `chmod u+rw` here to allow updating non rw files?
cleanup # dir/file.tmp
tf=$(mktemp)
filter_file &&
{ cp $backup -- "$tf" "$file" || backup_err=1; fail=1; } # truncate and copy
test $backup_err = 0 && test "$preserve_times" &&
{ touch "$file" --reference="$tf" || fail=1; }
fi
done
exit $fail