#!/bin/zsh # there is no posix stat(1) and the implementations are riddled with locale-speciffic stuff # better use sane version from zsh setopt extendedglob globdots zmodload zsh/stat typeset -A ftypes hardlinks s ftypes=( # convert hex type to mnemonic character C s # socket c s A l # symbolic link a l 8 f # regular file 6 b # block device 4 d # directory 2 u # character device 1 p # FIFO ) delim='' statement() { # start on new line for multiline statements - more readable if [[ -n delim && $1 == *$'\n'* ]]; then delim=$'\n' fi printf '%s%s' $delim ${1//$'\n'/$'\n\t'} delim=${2:-$'\t'} } statement_end() { printf '\n' delim='' } ROOT=${${${ROOT:-$PWD}:A}%/} : ${compact:=1} : ${print_m:=1} : ${print_o:=1} : ${print_c:=1} if (($+commands[file])) && (($+commands[base64])); then : ${print_b:=1} else : ${print_b:=0} if (($+commands[file])) && (($+commands[xxd])); then : ${print_x:=1} fi fi : ${print_x:=0} : ${max_bin_size:=} fnames=( ) for arg in "${@:-$ROOT}"; do fnames+=( $arg:a ) [[ -d $arg ]] && fnames+=( $arg:a/**/* ) done for fname in $fnames; do if ! [[ $fname == $ROOT || $fname == $ROOT/* ]]; then printf "skipping out-of-root file: %s\n" "$fname" >&2 continue fi relname=${${fname#$ROOT}#/} zstat -LH s $fname || continue ftype=$(( [##16] $s[mode] >> 12 )) fmode=$(( [##8] $s[mode] & 8#7777 )) t=$ftypes[$ftype] (($compact)) || printf '\n' if [[ $relname == *$'\t'* || $relname == *$'\n'* ]]; then statement $'P\t'$relname $'\t' else if (($compact)); then statement /$relname else statement /$relname $'\n' fi fi if [[ $t != d && $s[nlink] -gt 1 ]]; then id=$s[device]:$s[inode] if (($+hardlinks[$id])); then statement $'H\t'$hardlinks[$id] $'\n' continue else hardlinks[$id]=./$relname fi fi if [[ $t == [bu] ]]; then statement $t$(( $s[rdev] >> 8 )):$(( $s[rdev] & 255 )) elif [[ $t == l ]]; then statement $'l\t'$s[link] $'\t' elif [[ $t == f ]]; then if (($print_c)); then if (($s[size])) && (($print_b + $print_x)) && \ [[ $(file -bi $fname) != text/* ]]; then if [[ -n $max_bin_size && $s[size] -gt $max_bin_size ]]; then statement s$'\tSHA512:'${"$(sha512sum < $fname)"%% *} elif (($print_x)); then statement X$'\t'"$(xxd $fname)" $'\n' else statement B$'\t'"$(base64 <$fname)" $'\n' fi else IFS= read -r -d '' content <$fname flags='' if [[ $content == *$'\n' ]]; then content=${content%$'\n'} if [[ $content == *$'\n' ]]; then # force appending newline flags+=n fi else flags+=N fi if ! (($compact)) || [[ $content == *$'\t'* || $content == *$'\n'* ]]; then statement C$flags$'\t'$content $'\n' else statement c$flags$'\t'$content fi fi else statement f fi else statement $t fi (($print_o)) && statement o$s[uid]:$s[gid] (($print_m)) && statement m$fmode statement_end done # while getopts omcaOMC o # do case "$o" in # ([?]) # print >&2 "Usage: $(basename "$0") [-omcaOMC] [dir [...]]" # exit 1;; # esac # done # find "${@:-$PWD}" -exec stat -c '%F %U:%G %a %h:%i:%d %t:%T %n' '{}' + | awk ' # BEGIN { # FS="\t" # } # /\t/ # '