265 lines
6.5 KiB
Bash
Executable file
265 lines
6.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Purpose: plain text tar format
|
|
# Limitations: - only suitable for text files, directories, and symlinks
|
|
# - stores only filename, content, and mode
|
|
# - not designed for untrusted input
|
|
|
|
# Note: must work with bash version 3.2 (macOS)
|
|
|
|
set -o errexit -o nounset
|
|
|
|
# Sanitize environment (for instance, standard sorting of glob matches)
|
|
export LC_ALL=C
|
|
|
|
path=""
|
|
CMD=""
|
|
|
|
function usage {
|
|
bname=$(basename "$0")
|
|
cat << USAGE
|
|
Usage: $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive)
|
|
$bname -t -f <ARCHIVE> (list archive contents)
|
|
$bname [-C <DIR>] -x -f <ARCHIVE> (extract archive)
|
|
|
|
Options:
|
|
-C <DIR> (change directory)
|
|
|
|
Example: Change to sysfs directory, create ttar file from fixtures directory
|
|
$bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
|
|
USAGE
|
|
exit "$1"
|
|
}
|
|
|
|
function vecho {
|
|
if [ "${VERBOSE:-}" == "yes" ]; then
|
|
echo >&7 "$@"
|
|
fi
|
|
}
|
|
|
|
function set_cmd {
|
|
if [ -n "$CMD" ]; then
|
|
echo "ERROR: more than one command given"
|
|
echo
|
|
usage 2
|
|
fi
|
|
CMD=$1
|
|
}
|
|
|
|
while getopts :cf:htxvC: opt; do
|
|
case $opt in
|
|
c)
|
|
set_cmd "create"
|
|
;;
|
|
f)
|
|
ARCHIVE=$OPTARG
|
|
;;
|
|
h)
|
|
usage 0
|
|
;;
|
|
t)
|
|
set_cmd "list"
|
|
;;
|
|
x)
|
|
set_cmd "extract"
|
|
;;
|
|
v)
|
|
VERBOSE=yes
|
|
exec 7>&1
|
|
;;
|
|
C)
|
|
CDIR=$OPTARG
|
|
;;
|
|
*)
|
|
echo >&2 "ERROR: invalid option -$OPTARG"
|
|
echo
|
|
usage 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Remove processed options from arguments
|
|
shift $(( OPTIND - 1 ));
|
|
|
|
if [ "${CMD:-}" == "" ]; then
|
|
echo >&2 "ERROR: no command given"
|
|
echo
|
|
usage 1
|
|
elif [ "${ARCHIVE:-}" == "" ]; then
|
|
echo >&2 "ERROR: no archive name given"
|
|
echo
|
|
usage 1
|
|
fi
|
|
|
|
function list {
|
|
local path=""
|
|
local size=0
|
|
local line_no=0
|
|
local ttar_file=$1
|
|
if [ -n "${2:-}" ]; then
|
|
echo >&2 "ERROR: too many arguments."
|
|
echo
|
|
usage 1
|
|
fi
|
|
if [ ! -e "$ttar_file" ]; then
|
|
echo >&2 "ERROR: file not found ($ttar_file)"
|
|
echo
|
|
usage 1
|
|
fi
|
|
while read -r line; do
|
|
line_no=$(( line_no + 1 ))
|
|
if [ $size -gt 0 ]; then
|
|
size=$(( size - 1 ))
|
|
continue
|
|
fi
|
|
if [[ $line =~ ^Path:\ (.*)$ ]]; then
|
|
path=${BASH_REMATCH[1]}
|
|
elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
|
|
size=${BASH_REMATCH[1]}
|
|
echo "$path"
|
|
elif [[ $line =~ ^Directory:\ (.*)$ ]]; then
|
|
path=${BASH_REMATCH[1]}
|
|
echo "$path/"
|
|
elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then
|
|
echo "$path -> ${BASH_REMATCH[1]}"
|
|
fi
|
|
done < "$ttar_file"
|
|
}
|
|
|
|
function extract {
|
|
local path=""
|
|
local size=0
|
|
local line_no=0
|
|
local ttar_file=$1
|
|
if [ -n "${2:-}" ]; then
|
|
echo >&2 "ERROR: too many arguments."
|
|
echo
|
|
usage 1
|
|
fi
|
|
if [ ! -e "$ttar_file" ]; then
|
|
echo >&2 "ERROR: file not found ($ttar_file)"
|
|
echo
|
|
usage 1
|
|
fi
|
|
while IFS= read -r line; do
|
|
line_no=$(( line_no + 1 ))
|
|
if [ "$size" -gt 0 ]; then
|
|
echo "$line" >> "$path"
|
|
size=$(( size - 1 ))
|
|
continue
|
|
fi
|
|
if [[ $line =~ ^Path:\ (.*)$ ]]; then
|
|
path=${BASH_REMATCH[1]}
|
|
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
rm "$path"
|
|
fi
|
|
elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
|
|
size=${BASH_REMATCH[1]}
|
|
# Create file even if it is zero-length.
|
|
touch "$path"
|
|
vecho " $path"
|
|
elif [[ $line =~ ^Mode:\ (.*)$ ]]; then
|
|
mode=${BASH_REMATCH[1]}
|
|
chmod "$mode" "$path"
|
|
vecho "$mode"
|
|
elif [[ $line =~ ^Directory:\ (.*)$ ]]; then
|
|
path=${BASH_REMATCH[1]}
|
|
mkdir -p "$path"
|
|
vecho " $path/"
|
|
elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then
|
|
ln -s "${BASH_REMATCH[1]}" "$path"
|
|
vecho " $path -> ${BASH_REMATCH[1]}"
|
|
elif [[ $line =~ ^# ]]; then
|
|
# Ignore comments between files
|
|
continue
|
|
else
|
|
echo >&2 "ERROR: Unknown keyword on line $line_no: $line"
|
|
exit 1
|
|
fi
|
|
done < "$ttar_file"
|
|
}
|
|
|
|
function div {
|
|
echo "# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" \
|
|
"- - - - - -"
|
|
}
|
|
|
|
function get_mode {
|
|
local mfile=$1
|
|
if [ -z "${STAT_OPTION:-}" ]; then
|
|
if stat -c '%a' "$mfile" >/dev/null 2>&1; then
|
|
STAT_OPTION='-c'
|
|
STAT_FORMAT='%a'
|
|
else
|
|
STAT_OPTION='-f'
|
|
STAT_FORMAT='%A'
|
|
fi
|
|
fi
|
|
stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile"
|
|
}
|
|
|
|
function _create {
|
|
shopt -s nullglob
|
|
local mode
|
|
while (( "$#" )); do
|
|
file=$1
|
|
if [ -L "$file" ]; then
|
|
echo "Path: $file"
|
|
symlinkTo=$(readlink "$file")
|
|
echo "SymlinkTo: $symlinkTo"
|
|
vecho " $file -> $symlinkTo"
|
|
div
|
|
elif [ -d "$file" ]; then
|
|
# Strip trailing slash (if there is one)
|
|
file=${file%/}
|
|
echo "Directory: $file"
|
|
mode=$(get_mode "$file")
|
|
echo "Mode: $mode"
|
|
vecho "$mode $file/"
|
|
div
|
|
# Find all files and dirs, including hidden/dot files
|
|
for x in "$file/"{*,.[^.]*}; do
|
|
_create "$x"
|
|
done
|
|
elif [ -f "$file" ]; then
|
|
echo "Path: $file"
|
|
lines=$(wc -l "$file"|awk '{print $1}')
|
|
echo "Lines: $lines"
|
|
cat "$file"
|
|
mode=$(get_mode "$file")
|
|
echo "Mode: $mode"
|
|
vecho "$mode $file"
|
|
div
|
|
else
|
|
echo >&2 "ERROR: file not found ($file in $(pwd))"
|
|
exit 2
|
|
fi
|
|
shift
|
|
done
|
|
}
|
|
|
|
function create {
|
|
ttar_file=$1
|
|
shift
|
|
if [ -z "${1:-}" ]; then
|
|
echo >&2 "ERROR: missing arguments."
|
|
echo
|
|
usage 1
|
|
fi
|
|
if [ -e "$ttar_file" ]; then
|
|
rm "$ttar_file"
|
|
fi
|
|
exec > "$ttar_file"
|
|
_create "$@"
|
|
}
|
|
|
|
if [ -n "${CDIR:-}" ]; then
|
|
if [[ "$ARCHIVE" != /* ]]; then
|
|
# Relative path: preserve the archive's location before changing
|
|
# directory
|
|
ARCHIVE="$(pwd)/$ARCHIVE"
|
|
fi
|
|
cd "$CDIR"
|
|
fi
|
|
|
|
"$CMD" "$ARCHIVE" "$@"
|