#!/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