|
#!/bin/bash
# VIP - "VI/Preserve" Edit one or more files and preserve their modification
# timestamps and inode number
# The modification time is not perfectly preserved; it is incremented by one
# second so rsync and similar utilities that examine timestamps will know the
# file has actually changed.
# BUUS: This script is part of Brian's Useful Utilities Set
function die {
echo "$1" >&2
exit 1
}
[ -z "$1" ] && die "usage: vip filename [filename ...]"
DEBUG=false
if [ "$1" = -d ]; then DEBUG=true; shift; fi
[ -d /dev/shm ] && RAMDISK_SW=true || RAMDISK_SW=false
typeset -i FILE_NUM=0
declare -a FILE_LIST # List of files we're editing
declare -a FILE_MTIME_SEC # Timestamps on those files (one second granularity)
declare -a FILE_MTIME_FINE # Timestamps on those files (fine granularity)
declare -a FILE_EDIT_LIST # Files as they were copied for editing
WARN_SW=false
EDIT_SW=false # true = we found at least one file to edit
export TZ=Z
# Figure out which files from the command line list we can edit
for FILE in "$@"
do
$DEBUG && echo "debug: Considering \"$FILE\""
while [ -L "$FILE" ]; do FILE="$(readlink "$FILE")"; done
if [ ! -f $FILE ]
then
echo "vip: $FILE: not found -- skipping"
WARN_SW=true
elif [ ! -w "$FILE" ]
then
echo "vip: $FILE: no write permission -- skipping" >&2
WARN_SW=true
else
FILE_LIST[$FILE_NUM]="$FILE"
$DEBUG && echo "debug: OK to edit as number $FILE_NUM"
FILE_NUM=$((FILE_NUM+1))
EDIT_SW=true
fi
done
# Pause if there were warnings but we also have files we can edit
if $WARN_SW && $EDIT_SW
then
echo -n "Press Enter to continue: "; read JUNK
fi
# Prepare each file for editing
N=0
for FILE in "${FILE_LIST[@]}"
do
eval $(stat --format 'F_SIZE=%s F_MTIME_SEC=%Y F_MTIME_FINE="%y"' "$FILE")
# 2016-02-18 18:32:02.141592600 -0600 <-- F_MTIME_FINE
# 01...+....1....+....2....+....3....+
MTIME_FRACTIONAL_PART=""
FILE_MTIME_SEC[$N]=${F_MTIME_SEC}
FILE_MTIME_FINE[$N]=${F_MTIME_FINE}
[ "${#F_MTIME_FINE}" -gt 19 ] && MTIME_FRACTIONAL_PART=".${F_MTIME_FINE:20:9}"
FILE_MTIME_FRACTIONAL_PART[$N]=${MTIME_FRACTIONAL_PART}
FILE_EDIT_FN="$(basename "$FILE")"
if $RAMDISK_SW && [ $F_SIZE -lt 1048576 ]
then
FILE_EDIT_FN="/dev/shm/__$N.$FILE_EDIT_FN"
else
FILE_EDIT_FN="/tmp/__$N.$FILE_EDIT_FN"
fi
$DEBUG && echo "debug: [$N] $FILE, copy to \"$FILE_EDIT_FN\""
cp -a "$FILE" "$FILE_EDIT_FN"
FILE_EDIT_LIST[$N]="$FILE_EDIT_FN"
N=$((N+1))
done
# Edit the copied files
$DEBUG && $EDIT_SW && echo "debug: starting vim"
$EDIT_SW && vim "${FILE_EDIT_LIST[@]}"
# Cat the edited files back to the original files (which keeps the original
# file on the same inode) and update its timestamp
N=0
for FILE in "${FILE_LIST[@]}"
do
EDIT_FN="${FILE_EDIT_LIST[$N]}"
EDIT_MTIME_SEC="$(stat --format '%Y' "$EDIT_FN")"
if $DEBUG
then
echo "debug: considering \"$FILE\""
echo "debug: * Original mtime: ${FILE_MTIME_SEC[$N]} | ${FILE_MTIME_FINE[$N]}"
echo "debug: * Edited mtime: $EDIT_MTIME_SEC"
fi
if [ "${FILE_MTIME_SEC[$N]}" = "$EDIT_MTIME_SEC" ]
then
echo "$FILE: not changed"
else
$DEBUG && echo "debug: * cat from \"$EDIT_FN\""
cat "$EDIT_FN" >"$FILE"
X_MTIME="${FILE_MTIME_SEC[$N]}${FILE_MTIME_FRACTIONAL_PART[$N]}"
$DEBUG && echo "debug: * touch --date @(${X_MTIME} + 1) \"$FILE\""
touch --date @$((FILE_MTIME_SEC[$N] + 1))${FILE_MTIME_FRACTIONAL_PART[$N]} "$FILE"
$DEBUG && echo "debug: ${FILE}: $(stat -c '%y' $FILE)"
fi
rm -f "$EDIT_FN"
N=$((N+1))
done
# vim: tabstop=4
|