fslist3 (4609B)
1 #!/bin/zsh 2 setopt no_unset warn_create_global no_multibyte 3 zmodload zsh/stat 4 5 typeset -gA ftypes hardlinks s 6 typeset -g delim find fname ftype fmode 7 8 ftypes=( # convert hex type to mnemonic character 9 C s # socket 10 c s 11 A l # symbolic link 12 a l 13 8 f # regular file 14 6 b # block device 15 4 d # directory 16 2 u # character device 17 1 p # FIFO 18 ) 19 20 ### Defaults for options passed by env {{{1 21 22 # TODO: make into command-line arguments 23 : ${compact:=1} 24 : ${print_m:=1} 25 : ${print_o:=1} 26 : ${print_c:=1} 27 : ${print_s:=1} 28 if (($+commands[file])) && (($+commands[base64])); then 29 : ${print_b:=1} 30 else 31 : ${print_b:=0} 32 if (($+commands[file])) && (($+commands[xxd])); then 33 : ${print_x:=1} 34 fi 35 fi 36 : ${print_x:=0} 37 : ${max_bin_size:=} 38 : ${max_newline_size:=1024} 39 40 ### Generic helpers {{{1 41 42 die() { 43 printf '%s\n' "$@" 44 exit 1 45 } 46 47 ### FileSet writer functions {{{1 48 49 statement() { 50 # start on new line for multiline statements - more readable 51 if [[ -n "$delim" && $1 == *$'\n'* ]]; then 52 delim=$'\n' 53 fi 54 printf '%s%s' $delim ${1//$'\n'/$'\n\t'} 55 delim=${2:-$'\t'} 56 } 57 58 statement_end() { 59 printf '\n' 60 delim='' 61 } 62 63 64 process_file() { 65 local -a find_info 66 local t fmode owner size filename 67 find_info=( $=1 ) 68 t=$find_info[1] 69 fmode=$find_info[2] 70 owner=$find_info[3] 71 size=$find_info[4] 72 73 # --- print stuff --- 74 75 (($compact)) || printf '\n' 76 77 filename="$fname" 78 if [[ $filename == . ]]; then 79 filename=/ 80 elif [[ $filename == ./* ]]; then 81 filename=$filename[2,-1] 82 fi 83 filename=${filename#/} 84 85 # printf "%s %s %s\n" >&2 $t $fmode "${(qqq)filename}" 86 87 if [[ $filename == *$'\t'* || $filename == *$'\n'* ]]; then 88 statement $'P\t'$filename $'\t' 89 else 90 if (($compact)); then 91 statement /${filename} 92 else 93 statement /${filename} $'\n' 94 fi 95 fi 96 97 # Note: not supporting hardlinks (yet) 98 99 if [[ $t == [bu] ]]; then 100 zstat -LH s $fname || die "stat failed on ${(qqq)fname}" 101 statement $t$(( $s[rdev] >> 8 )):$(( $s[rdev] & 255 )) 102 elif [[ $t == l ]]; then 103 zstat -LH s $fname || die "stat failed on ${(qqq)fname}" 104 statement $'l\t'$s[link] $'\t' 105 elif [[ $t == f ]]; then 106 if (($print_c)); then 107 process_file_data $size 108 else 109 statement f 110 if (($print_s)); then 111 statement s$'\tSHA512:'${"$(sha512sum <$fname)"%% *} \ 112 || die "Could not read ${(qqq)fname}" 113 fi 114 fi 115 else 116 statement $t 117 fi 118 119 (($print_o)) && statement o$owner 120 (($print_m)) && statement m$fmode 121 statement_end 122 } 123 124 process_file_data() { 125 local size 126 size=$1 127 if ! (($size)); then 128 statement cN$'\t' # empty file 129 return 130 fi 131 132 # print binary representation? 133 if (($print_b | $print_x)) && \ 134 [[ $(file -bi "$fname") != text/* ]] 135 then 136 if (($size > 256)); then 137 if [[ -n $max_bin_size && $size -gt $max_bin_size ]]; then 138 statement s$'\tSHA512:'${"$(sha512sum <$fname)"%% *} \ 139 || die "Could not read ${(qqq)fname}" 140 elif (($print_x)); then 141 if [[ -n "$delim" ]]; then 142 printf '\nX' 143 else 144 printf 'X' 145 fi 146 xxd <$fname | sed 's/^/\t/' 147 ((${(j.|.)pipestatus})) && die "Could not read ${(qqq)fname}" 148 statement_end 149 else 150 if [[ -n "$delim" ]]; then 151 printf '\nB' 152 else 153 printf 'B' 154 fi 155 base64 <$fname | sed 's/^/\t/' 156 ((${(j.|.)pipestatus})) && die "Could not read ${(qqq)fname}" 157 statement_end 158 fi 159 else 160 if [[ -n $max_bin_size && $size -gt $max_bin_size ]]; then 161 statement s$'\tSHA512:'${"$(sha512sum <$fname)"%% *} 162 elif (($print_x)); then 163 statement X$'\t'"$(xxd <$fname)" $'\n' 164 else 165 statement B$'\t'"$(base64 <$fname)" $'\n' 166 fi 167 fi 168 return 169 fi 170 # print text 171 172 # if file is longer than this, always use CN 173 if (($size > $max_newline_size)); then 174 if [[ -n "$delim" ]]; then 175 printf '\nCN\t' 176 else 177 printf 'CN\t' 178 fi 179 # Swap NL with @ so trailing newline is handled correctly 180 tr <$fname '\n@' '@\n' \ 181 | sed 's/@/@\t/g' \ 182 | tr '@\n' '\n@' 183 ((${(j.|.)pipestatus})) && die "Could not read ${(qqq)fname}" 184 statement_end 185 return 186 fi 187 188 # read and then print out, determinig trailing newline flags 189 local content flags 190 content="$(<$fname)" 191 flags='' 192 if [[ $content == *$'\n' ]]; then 193 content=${content%$'\n'} 194 if [[ $content == *$'\n' ]]; then 195 # force appending newline 196 flags+=n 197 fi 198 else 199 flags+=N 200 fi 201 if ! (($compact)) || [[ $content == *$'\t'* || $content == *$'\n'* ]]; then 202 statement C$flags$'\t'$content $'\n' 203 else 204 statement c$flags$'\t'$content 205 fi 206 } 207 208 ### Mainloop {{{1 209 if (($+ROOT)) && [[ -n $ROOT ]]; then 210 cd $ROOT || exit $? 211 fi 212 213 find "$@" -printf '%y %m %U:%G %s\t%p\0' \ 214 | sort -z -t $'\t' -k 2 \ 215 | while IFS=$'\t' read -r -d $'\0' find fname 216 do 217 process_file "$find" "$fname" 218 done