├── Makefile ├── README ├── bulkrename ├── bulkrename.1 ├── pick ├── pick.1 ├── preview ├── preview.1 ├── skel ├── skel.1 ├── trash ├── trash.1 ├── unpack ├── unpack.1 └── untrash /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | MANPREFIX ?= ${PREFIX}/share/man 3 | 4 | PROGS = bulkrename \ 5 | pick \ 6 | preview \ 7 | skel \ 8 | trash \ 9 | untrash \ 10 | unpack \ 11 | unpreview 12 | 13 | MANS = bulkrename.1 \ 14 | pick.1 \ 15 | preview.1 \ 16 | skel.1 \ 17 | trash.1 \ 18 | unpack.1 19 | 20 | all: 21 | @echo no need to build 22 | 23 | unpreview: preview 24 | cp preview unpreview 25 | 26 | install: unpreview 27 | install -D -m 755 ${PROGS} ${DESTDIR}${PREFIX}/bin/ 28 | install -D -m 644 ${MANS} ${DESTDIR}${MANPREFIX}/man1/ 29 | 30 | .PHONY: all install 31 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | fmutils 2 | 3 | fmutils is a set of public domain POSIX scripts implementing some file 4 | management related utilities. There are man pages for each script. 5 | 6 | The scripts are the following. 7 | • bulkrename: Bulk rename files with your text editor. 8 | • pick: Interactivelly select options. 9 | • preview: Generate text preview of a file to be used by lf(1) or ranger(1). 10 | • skel: Create a template file in the current directory. 11 | • trash: Move files to trash following XDG trash specification. 12 | • untrash: Remove files from trash following XDG trash specification. 13 | • unpack: Unarchive archived files (.zip, .rar, .tar.gz, etc). 14 | 15 | § Running FMUtils 16 | 17 | The following are examples of how to use FMUtils. 18 | 19 | Bulk rename all files in the current directory: 20 | $ bulkrename * 21 | 22 | Bulk rename all .jpg files in the ~/Downloads directory: 23 | $ bulkrename ~/Downloads/*.jpg 24 | 25 | Pick can be used to interactivelly remove files with rm(1). This 26 | example is equivalent to `rm -i`, but can be used with other commands 27 | that, unlike rm(1), does not provide a -i option. 28 | $ rm $(pick *) 29 | 30 | The previous example rely on the condition that each filename does not 31 | contain space on it. The following example avoid this by using a 32 | quotion option, such as -q on pick(1), and a pipe into xargs(1): 33 | $ pick -q * | xargs rm 34 | 35 | Add the following line to your ~/.config/lf/lfrc to use preview(1) as 36 | file previewer. 37 | set previewer preview 38 | 39 | Move all .jpg files to trash: 40 | $ trash *.jpg 41 | 42 | Remove all .jpg files from trash (the environment variable $TRASH is 43 | supposed to have the path of the trash) 44 | $ untrash $TRASH/files/*.jpg 45 | 46 | Extract the content of file.zip into your home directory. 47 | $ unpack file.zip $HOME 48 | 49 | 50 | § License 51 | 52 | This software is in public domain and is provided AS IS, with NO WARRANTY. 53 | -------------------------------------------------------------------------------- /bulkrename: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # bulkrename: bulk rename files using $EDITOR and temporary files 3 | # this file in public domain 4 | 5 | EDITOR="${EDITOR:-"${VISUAL:-"vi"}"}" 6 | 7 | err() { 8 | printf "%s: %s\n" "${0##*/}" "$@" >&2 9 | exit 1 10 | } 11 | 12 | hascollision() { 13 | i=0 14 | for arg 15 | do 16 | if test "$i" -lt "$nargs" 17 | then 18 | test "$target" = "$arg" && return 19 | else 20 | break 21 | fi 22 | i=$((i+1)) 23 | done 24 | false 25 | } 26 | 27 | # create temporary files 28 | if ! tmpfile="$(mktemp)" 29 | then 30 | err "could not make temporary file" 31 | fi 32 | trap 'rm -f "$tmpfile"' EXIT 33 | 34 | # if no argument is passed, fill $@ with lines from stdin 35 | if [ $# -eq 0 ] 36 | then # run on stdin 37 | while read -r line 38 | do 39 | set -- "$@" "$line" 40 | done 41 | fi 42 | 43 | # check if arguments are valid and write them to tmpfile 44 | nargs="$#" 45 | for arg 46 | do 47 | case "$arg" in 48 | ("") 49 | err "empty argument" 50 | ;; 51 | (*[[:cntrl:]]*) 52 | err "control characters in filenames are not supported" 53 | ;; 54 | ([[:space:]]*|*[[:space:]]) 55 | err "filenames beginning or ending in space is not supported" 56 | ;; 57 | esac 58 | if ! test -e "$arg" 59 | then 60 | err "$arg: file does not exist" 61 | fi 62 | printf "%s\n" "$arg" >> "$tmpfile" 63 | done 64 | 65 | # call editor on tmpfile 66 | "$EDITOR" -- "$tmpfile" %s\n" "$2" "$3" 122 | shift 3 123 | ;; 124 | ("mv1") 125 | set -- "$@" "mv2" "$2" "$3" 126 | shift 3 127 | ;; 128 | ("mv2") 129 | break 130 | ;; 131 | (*) 132 | err "$1: unknown command" 133 | ;; 134 | esac 135 | done 136 | 137 | # perform the commands specified in the argument list (the final ones) 138 | while test "$#" -gt 0 139 | do 140 | case "$1" in 141 | ("mv2") 142 | mv -- "$2" "$3" 143 | printf "%s -> %s\n" "$2" "$3" 144 | shift 3 145 | ;; 146 | (*) 147 | err "$1: unknown command" 148 | ;; 149 | esac 150 | done 151 | -------------------------------------------------------------------------------- /bulkrename.1: -------------------------------------------------------------------------------- 1 | .Dd September 23, 2022 2 | .Dt BULKRENAME 1 3 | .Os 4 | .Sh NAME 5 | .Nm bulkrename 6 | .Nd edit filenames interactively within editor 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Ar file ... 10 | .Sh DESCRIPTION 11 | .Nm 12 | receives filenames from its arguments (one per argument) or from stdin 13 | (one per line) and opens an editor to edit them; 14 | then, it renames each file to the corresponding edited one. 15 | .Pp 16 | If there are no arguments, 17 | .Nm 18 | operates on stdin, 19 | reading one filename per line. 20 | If there are arguments, each argument is read as a filename. 21 | .Pp 22 | The environment variables 23 | .Ev EDITOR 24 | and 25 | .Ev VISUAL 26 | are checked, in this order, for an editor program. 27 | If both are unset, use 28 | .Xr vi 1 29 | by default. 30 | .Sh ENVIRONMENT 31 | The following environment variables affect the execution of 32 | .Nm Ns : 33 | .Bl -tag -width Ds 34 | .It Ev EDITOR 35 | The editor to be used. 36 | .It Ev VISUAL 37 | The editor to be used if 38 | .Ev EDITOR 39 | is not set. 40 | .El 41 | .Sh EXAMPLES 42 | Open editor to rename all 43 | .Pa "*.jpg" 44 | files: 45 | .Bd -literal -offset indent 46 | $ bulkrename *.jpg 47 | .ED 48 | .Sh SEE ALSO 49 | .Xr vi 1 50 | -------------------------------------------------------------------------------- /pick: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # pick: select arguments 3 | # this file in public domain 4 | 5 | quoting_style="${QUOTING_STYLE:-"literal"}" 6 | delimiter=' 7 | ' 8 | 9 | usage() { 10 | echo "usage: pick [-bnq] [-d delimiter] arg..." >&2 11 | exit 1 12 | } 13 | 14 | pick() { 15 | # print prompt to terminal and read response from terminal 16 | printf "%s? " "$1" >/dev/tty 17 | read -r response nm 20 | receives 21 | .Ic "-" 22 | (an hyphen) 23 | as only argument, each option is a line from the standard input; 24 | otherwise, each option is a different argument. 25 | .Pp 26 | The options are as follows: 27 | .Bl -tag -width Ds 28 | .It Fl b 29 | Escape whitespace by printing a backslash before it. 30 | This option overrides the 31 | .Ev QUOTING_STYLE 32 | environment variable. 33 | This option is incompatible with 34 | .Fl q . 35 | .It Fl d Ar delimiter 36 | Use 37 | .Ar delimiter 38 | as argument separator, rather than newline. 39 | This option is incompatible with 40 | .Fl n . 41 | .It Fl q 42 | Quote each option with single quotes just as shell quoting. 43 | This option overrides the 44 | .Ev QUOTING_STYLE 45 | environment variable. 46 | This option is incompatible with 47 | .Fl b . 48 | .It Fl n 49 | Do not separate options by newline, separate by a single space, instead. 50 | This option is incompatible with 51 | .Fl d . 52 | .El 53 | .Pp 54 | .Sh ENVIRONMENT 55 | The following environment variables affect the execution of 56 | .Nm 57 | .Bl -tag -width Ds 58 | .It Ev QUOTING_STYLE 59 | If set to 60 | .Dq "escape" , 61 | .Nm 62 | behaves as if the option 63 | .Fl b 64 | is set, thus whitespaces are escaped with backslashes. 65 | If set to 66 | .Dq "shell" , 67 | .Nm 68 | behaves as if the option 69 | .Fl q 70 | is set, thus options are quoted between single quotes as in shell quoting. 71 | If set to 72 | .Dq "literal" , 73 | options are output as is. 74 | .Sh EXAMPLES 75 | .Nm 76 | can be used to interactivelly remove files with 77 | .Xr rm 1 . 78 | This example is equivalent to rm's 79 | .Fl i 80 | (interactive) option, 81 | but can be used with other commands that, unlike 82 | .Xr rm 1 , 83 | does not provide a 84 | .Fl i 85 | option. 86 | .Bd -literal -offset indent 87 | $ rm `pick *` 88 | .Ed 89 | .Pp 90 | The following example is equivalent to the previous one, but uses the 91 | standard input fed by the 92 | .Xr ls 1 93 | utility. 94 | .Bd -literal -offset indent 95 | $ rm `ls | pick -` 96 | .Ed 97 | .Pp 98 | The previous examples rely on the condition that each filename does not contain space in it. 99 | If a file contains space, it would be considered as a field separator. 100 | One option to avoid this is using 101 | .Xr xargs 1 102 | with the option 103 | .Fl d Cm "\en" 104 | to consider only the newline output by 105 | .Nm 106 | as the field separator. 107 | .Bd -literal -offset indent 108 | $ pick * | xargs -d '\\n' rm 109 | .Ed 110 | .Pp 111 | Another option is to use a quoting option, such as 112 | .Fl q 113 | on 114 | .Nm , 115 | and pipe it into 116 | .Xr xargs 1 : 117 | .Bd -literal -offset indent 118 | $ pick -q * | xargs rm 119 | .Ed 120 | .Sh SEE ALSO 121 | .Xr xargs 1 122 | .Rs 123 | .%A Brian W. Kernighan 124 | .%A Rob Pike 125 | .%B "The UNIX Programming Environment" 126 | .%I "Prentice Hall" 127 | .%D 1984 128 | .Re 129 | .Sh HISTORY 130 | An 131 | .Nm 132 | utility appeared as an exemple in the book 133 | .Rs 134 | .%B "The UNIX Programming Environment" 135 | .Re 136 | -------------------------------------------------------------------------------- /preview: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this file in public domain 3 | 4 | previewdir="${CACHEDIR:-"${XDG_CACHE_HOME:-"$HOME/.cache"}"}/preview" 5 | 6 | # show usage 7 | usage() { 8 | echo "usage: preview filename [w h x y]" >&2 9 | exit 1 10 | } 11 | 12 | # get file extension 13 | getextension() { 14 | echo "${1##*.}" | tr '[:upper:]' '[:lower:]' 15 | } 16 | 17 | # get file mimetype 18 | getfilemime() { 19 | file -ib "$@" | cut -d';' -f1 20 | } 21 | 22 | # get cached file 23 | getcache() { 24 | mkdir -p "$previewdir" 25 | printf "%s/%s" "$previewdir" "$(stat "$1" | sha256).png" 26 | } 27 | 28 | # preview image 29 | img() { 30 | PATH="$PATH:/usr/local/lib/w3m/" 31 | PATH="$PATH:/usr/local/libexec/w3m/" 32 | PATH="$PATH:/usr/local/lib64/w3m/" 33 | PATH="$PATH:/usr/local/libexec64/w3m/" 34 | PATH="$PATH:/usr/lib/w3m/" 35 | PATH="$PATH:/usr/libexec/w3m/" 36 | PATH="$PATH:/usr/lib64/w3m/" 37 | PATH="$PATH:/usr/libexec64/w3m/" 38 | filename="$1" 39 | 40 | { 41 | # get information about terminal X11 window 42 | xdotool getwindowgeometry --shell "$WINDOWID" 2>/dev/null | cut -d= -f2 43 | 44 | # get information about terminal character column and row 45 | stty -f /dev/tty size 46 | 47 | # get information about image 48 | printf "5;%s\n" "$filename" | w3mimgdisplay 49 | } | tr '\n' ' ' | { 50 | read -r WIN_ID WIN_X WIN_Y WIN_W WIN_H SCREEN LINES COLUMNS IMG_W IMG_H 51 | 52 | # do nothing on dummy variables, just for shellcheck to be happy 53 | : "$WIN_ID" "$WIN_X" "$WIN_Y" "$SCREEN" 54 | 55 | # compute stuff 56 | FONT_W="$(( WIN_W / COLUMNS ))" 57 | FONT_H="$(( WIN_H / LINES ))" 58 | 59 | # perform w3m(1) black magic 60 | if test "$cmdname" = "unpreview" 61 | then 62 | FINAL_X="$(( FONT_W * ( X + 1 ) ))" 63 | FINAL_Y="$(( FONT_H * ( Y + 1 ) ))" 64 | FINAL_W="$(( FONT_W * ( W - 2 ) ))" 65 | FINAL_H="$(( FONT_H * ( H - 2 ) ))" 66 | 67 | FINAL_W="$((FINAL_W + FONT_W))" 68 | FINAL_H="$((FINAL_H + FONT_H))" 69 | printf '6;%s;%s;%s;%s;\n4;\n3;\n' \ 70 | "$FINAL_X" "$FINAL_Y" "$FINAL_W" "$FINAL_H" | w3mimgdisplay 71 | else 72 | MAX_W="$(( FONT_W * (W - 3) ))" 73 | MAX_H="$(( FONT_H * (H - 3) ))" 74 | FINAL_X="$(( FONT_W * (X + 1) ))" 75 | FINAL_Y="$(( FONT_H * (Y + 1) ))" 76 | FINAL_W="$IMG_W" 77 | FINAL_H="$IMG_H" 78 | # resize image 79 | if test "$FINAL_H" -gt "$MAX_H" 80 | then 81 | FINAL_W="$(( FINAL_W * MAX_H / FINAL_H ))" 82 | FINAL_H="$MAX_H" 83 | fi 84 | if test "$FINAL_W" -gt "$MAX_W" 85 | then 86 | FINAL_H="$(( FINAL_H * MAX_W / FINAL_W ))" 87 | FINAL_W="$MAX_W" 88 | fi 89 | 90 | printf '6;%s;%s;%s;%s;\n0;1;%s;%s;%s;%s;;;;;%s\n4;\n3;\n' \ 91 | "$FINAL_X" "$FINAL_Y" "$FINAL_W" "$FINAL_H" \ 92 | "$FINAL_X" "$FINAL_Y" "$FINAL_W" "$FINAL_H" "$filename" | w3mimgdisplay 93 | fi 94 | } 95 | } 96 | 97 | # preview file and exit 98 | preview() { 99 | case "$2" in 100 | "") 101 | if test "$cmdname" != "unpreview" 102 | then 103 | echo '----- File Type Classification -----' 104 | file -b "$1" 105 | fi 106 | exit 107 | ;; 108 | a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\ 109 | rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip|7z) 110 | if test "$cmdname" != "unpreview" 111 | then 112 | bsdtar -tf "$1" 113 | fi 114 | exit 115 | ;; 116 | pdf) 117 | if test "$cmdname" != "unpreview" 118 | then 119 | pdftotext -layout -- "$1" - 120 | fi 121 | exit 122 | ;; 123 | htm|html|xhtml) 124 | if test "$cmdname" != "unpreview" 125 | then 126 | w3m -dump "$1" 127 | fi 128 | exit 129 | ;; 130 | 1|2|3|4|5|6|7|8|9) 131 | if test "$cmdname" != "unpreview" 132 | then 133 | MANWIDTH="$W" man -Tutf8 -l "$1" | sed 's/.//g' 134 | fi 135 | exit 136 | ;; 137 | text/*|*/xml|application/x-shellscript) 138 | cat "$1" 139 | exit 140 | ;; 141 | xpm|xbm) 142 | if test "$IMG" -eq 1 && cache="$(getcache "$1")" && [ -f "$cache" ] || convert "$1" "$cache" 143 | then 144 | img "$cache" 145 | exit 1 146 | elif test "$cmdname" != "unpreview" 147 | then 148 | cat "$1" 149 | exit 150 | fi 151 | ;; 152 | image/svg+xml) 153 | if test "$IMG" -eq 1 && cache="$(getcache "$1")" && [ -f "$cache" ] || rsvg-convert "$1" >"$cache" 154 | then 155 | img "$cache" 156 | exit 1 157 | elif test "$cmdname" != "unpreview" 158 | then 159 | cat "$1" 160 | exit 161 | fi 162 | ;; 163 | image/*) 164 | if test "$IMG" -eq 1 165 | then 166 | img "$1" 167 | exit 1 168 | elif test "$cmdname" != "unpreview" 169 | then 170 | exiftool "$1" 171 | exit 172 | fi 173 | ;; 174 | video/*) 175 | if test "$IMG" -eq 1 && cache="$(getcache "$1")" && [ -f "$cache" ] || ffmpegthumbnailer -i "$1" -o "$cache" -s 0 176 | then 177 | img "$cache" 178 | exit 1 179 | elif test "$cmdname" != "unpreview" 180 | then 181 | mediainfo "$1" 182 | exit 183 | fi 184 | exit 185 | ;; 186 | audio/*|application/octet-stream) 187 | if test "$cmdname" != "unpreview" 188 | then 189 | mediainfo "$1" 190 | fi 191 | exit 192 | ;; 193 | esac 194 | } 195 | 196 | cmdname="${0##*/}" 197 | case "$#" in 198 | 1) 199 | W=72 200 | H=20 201 | X=0 202 | Y=0 203 | IMG=0 204 | ;; 205 | 5) 206 | W=$2 207 | H=$3 208 | X=$4 209 | Y=$5 210 | IMG=1 211 | ;; 212 | *) 213 | usage 214 | ;; 215 | esac 216 | 217 | test -p "$1" && exit 1 218 | preview "$1" "$(getextension "$1")" # try first to preview by file extension 219 | preview "$1" "$(getfilemime "$1")" # then try to preview by mimetype 220 | preview "$1" # then just print its file type 221 | -------------------------------------------------------------------------------- /preview.1: -------------------------------------------------------------------------------- 1 | .Dd September 26, 2022 2 | .Dt PREVIEW 1 3 | .Os 4 | .Sh NAME 5 | .Nm preview , 6 | .Nm unpreview 7 | .Nd generate preview of file, and clear the preview 8 | .Sh SYNOPSIS 9 | .Nm preview 10 | .Ar file 11 | .Op Ar width Ar height Ar x Ar y 12 | .Nm unpreview 13 | .Ar file 14 | .Op Ar width Ar height Ar x Ar y 15 | .Sh DESCRIPTION 16 | .Nm preview 17 | generates a text containing data and/or metadata of a file, 18 | this text is intended to be used as a preview for that file. 19 | If the previewed file is a image, it draws the image on the terminal. 20 | .Pp 21 | .Nm unpreview 22 | clears the previewed text, if it is an image. 23 | .Pp 24 | The first argument is the file to be previewd. 25 | The second and third arguments are the height and width (in characters) 26 | where the preview text should be printed in the terminal. 27 | The fourth and fifth arguments are the line and column position 28 | where the preview text should be printed in the terminal. 29 | .Pp 30 | .Nm preview 31 | and 32 | .Nm unpreview 33 | are intended to be used by a file manager such as 34 | .Xr lf 1 35 | or 36 | .Xr ranger 1 . 37 | .Sh SEE ALSO 38 | .Xr lf 1 , 39 | .Xr ranger 1 40 | -------------------------------------------------------------------------------- /skel: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # skel - create a template file in the current directory 3 | # this file in public domain 4 | 5 | SKEL="${SKEL:-"${XDG_DATA_HOME:-"$HOME/.local/share"}/skel"}" 6 | 7 | usage() { 8 | echo "usage: skel [template [name]]" >&2 9 | exit 1 10 | } 11 | 12 | listskel() { 13 | ls -1 "$SKEL" 14 | return 0 15 | } 16 | 17 | createskel() { 18 | skelfile="${SKEL}/$1" 19 | destfile="${PWD}/$2" 20 | 21 | if [ ! -e "$skelfile" ] 22 | then 23 | printf "skel: %s: skeleton file does not exist\n" "$1" >&2 24 | exit 1 25 | fi 26 | 27 | if [ -e "$destfile" ] 28 | then 29 | printf "skel: %s: destination file already exists\n" "$2" >&2 30 | exit 1 31 | else 32 | echo cp -R -- "$skelfile" "$destfile" 33 | cp -R -- "$skelfile" "$destfile" 34 | fi 35 | } 36 | 37 | case $# in 38 | 0) 39 | listskel 40 | ;; 41 | 1) 42 | createskel "$1" "$1" 43 | ;; 44 | 2) 45 | createskel "$1" "$2" 46 | ;; 47 | *) 48 | usage 49 | ;; 50 | esac 51 | 52 | exit 0 53 | -------------------------------------------------------------------------------- /skel.1: -------------------------------------------------------------------------------- 1 | .Dd September 26, 2022 2 | .Dt SKEL 1 3 | .Os 4 | .Sh NAME 5 | .Nm skel 6 | .Nd create a template file in the current directory 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Ar template Op Ar name 10 | .Sh DESCRIPTION 11 | .Nm 12 | copies the specified skeleton file from the skeleton directory into the current directory. 13 | A skeleton file is a template file containing a sample of a file format. 14 | .Pp 15 | If 16 | .Nm 17 | receives no argument, it only lists the available skeleton files. 18 | If it receives argument, copies the 19 | .Ar template 20 | skeleton file into the current directory by the name 21 | .Ar name . 22 | .Sh ENVIRONMENT 23 | The following environment variables affect the execution of 24 | .Nm 25 | .Bl -tag -width Ds 26 | .It Ev SKEL 27 | The directory containing skeleton files. 28 | If not specified, use 29 | .Pa ~/.local/share/skel 30 | by default. 31 | .El 32 | .Sh FILES 33 | .Bl -tag -width Ds 34 | .Pa $SKEL/* 35 | Skeleton files. 36 | -------------------------------------------------------------------------------- /trash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # trash: move files to trash 3 | # this file in public domain 4 | 5 | trashdir="${TRASH:-"${XDG_DATA_HOME:-"$HOME/.local/share"}/Trash"}" 6 | retval=0 7 | IFS="/" 8 | 9 | usage() { 10 | echo "usage: trash file..." >&2 11 | exit 1 12 | } 13 | 14 | # make trash directory and set its mode 15 | mktrashdir() { 16 | if ! mkdir -p -- "$trashdir/files" "$trashdir/info" >/dev/null 2>&1 17 | then 18 | echo "trash: unable to create $trashdir" 19 | exit 1 20 | fi 21 | chmod 700 "$trashdir" 22 | } 23 | 24 | # canonicalize "//", "/./", and "/../" from $filename into $path 25 | setpath() { 26 | path="" 27 | case "$filename" in 28 | ([!/]*) filename="$PWD/$filename" ;; 29 | esac 30 | for segment in $filename 31 | do 32 | case "$segment" in 33 | ("") ;; 34 | (".") ;; 35 | ("..") path="${path%/*}" ;; 36 | (*) path="$path/$segment" ;; 37 | esac 38 | done 39 | } 40 | 41 | # break basename of $path into body and extension; set $newfile 42 | # to "body.ext" (or "body_N.ext", for a unique file) 43 | setnewfile() { 44 | basename="${path##*/}" 45 | body="${basename%.*}" 46 | ext="${basename##"$body"}" 47 | i="" 48 | p="" 49 | while test -e "$trashdir/files/$body$p$i$ext" 50 | do 51 | p="_" 52 | if [ -z "$i" ] 53 | then 54 | i=1 55 | else 56 | i=$((i + 1)) 57 | fi 58 | done 59 | newfile="$body$p$i$ext" 60 | } 61 | 62 | # make $trashdir/info/$newfile.trashinfo file containing the original 63 | # path of the trashed file and its date of deletion 64 | mktrashinfo() { 65 | cat > "$trashdir/info/$newfile.trashinfo" <<-END 66 | [Trash Info] 67 | Path=$path 68 | DeletionDate=$(date +"%FT%H:%M:%S") 69 | END 70 | } 71 | 72 | # move $path into $trashdir/files/$newfile 73 | movetotrash() { 74 | 75 | if ! mv -- "$path" "$trashdir/files/$newfile" >/dev/null 2>&1 76 | then 77 | echo "trash: $arg: could not move file to trash" >&2 78 | retval=1 79 | else 80 | mktrashinfo 81 | fi 82 | } 83 | 84 | [ $# -eq 0 ] && usage 85 | mktrashdir 86 | for arg 87 | do 88 | filename="$arg" 89 | setpath 90 | if test ! -e "$path" 91 | then 92 | echo "trash: $arg: no such file or directory" >&2 93 | retval=1 94 | else 95 | setnewfile 96 | movetotrash 97 | fi 98 | done 99 | exit $retval 100 | -------------------------------------------------------------------------------- /trash.1: -------------------------------------------------------------------------------- 1 | .Dd September 26, 2022 2 | .Dt TRASH 1 3 | .Os 4 | .Sh NAME 5 | .Nm trash , 6 | .Nm untrash 7 | .Nd move files to the trash can, and restore them 8 | .Sh SYNOPSIS 9 | .Nm trash 10 | .Ar file ... 11 | .Nm untrash 12 | .Ar file ... 13 | .Sh DESCRIPTION 14 | .Nm trash 15 | moves one or more files or directories to a directory called trash can, 16 | with a unique name resembling its original name; 17 | and creates a file containing information on where the file came from and 18 | the time it was moved to the trash can. 19 | .Pp 20 | .Nm untrash 21 | moves one or more files or directory from a directory called trash can 22 | to the directory it originally was before being moved to the trash can 23 | by 24 | .Nm trash . 25 | .Po 26 | In order to know where a trashed file came from, 27 | .Nm trash 28 | creates an information file with such information. 29 | .Sh ENVIRONMENT 30 | The following environment variables affect the execution of 31 | .Nm trash 32 | and 33 | .Nm untrash : 34 | .Bl -tag -width Ds 35 | .It Ev TRASH 36 | The directory containing the trash can for the partition of the user. 37 | If not set, defaults to 38 | .Pa "~/.local/share/Trash" . 39 | .Sh FILES 40 | The trash directory is created with the proper permissions the first time 41 | .Nm trash 42 | is used. 43 | It contains two subdirectories called 44 | .Pa "$TRASH/files/" 45 | and 46 | .Pa "$TRASH/info/" . 47 | .Bl -tag -width Ds 48 | .It Pa "$TRASH/files/*" 49 | This directory contains the files and directories that were trashed. 50 | When a file or directory is trashed, it is moved into this directory. 51 | Each file has a unique name equal to its original name plus a unique number. 52 | .It Pa "$TRASH/info/*.trashinfo" 53 | This directory contains an 54 | .Dq "information file" 55 | for every file and directory in 56 | .Pa "$TRASH/files/" . 57 | Each information file has exactly the same name as the corresponding 58 | file or directory in 59 | .Pa "$TRASH/files/" , 60 | plus the extension 61 | .Dq "trashinfo" 62 | Each information file contains the original name of the trashed file 63 | and the time it was trashed. 64 | .Sh SEE ALSO 65 | .Xr rm 1 66 | -------------------------------------------------------------------------------- /unpack: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # unpack - unarchive archive files 3 | # this file in public domain 4 | 5 | err() 6 | { 7 | printf 'unpack: %s\n' "$1" >&2 8 | exit 1 9 | } 10 | 11 | usage() 12 | { 13 | echo "usage: unpack file [destdir]" >&2 14 | exit 1 15 | } 16 | 17 | case "$#" in 18 | (1) set -- "$1" "$PWD" ;; 19 | (2) ;; 20 | (*) usage ;; 21 | esac 22 | 23 | test -f "$1" || err "$1: not a regular file" 24 | test -d "$2" || err "$2: not a regular directory" 25 | 26 | case "$(file -ib -- "$1" | cut -d';' -f1 | cut -d'/' -f2)" in 27 | gzip|x-gzip) 28 | tar xzvf "$1" -C "$2" >/dev/null || err "extraction failed" 29 | ;; 30 | bzip2|x-bzip2) 31 | tar xjvf "$1" -C "$2" >/dev/null || err "extraction failed" 32 | ;; 33 | tar|x-tar) 34 | tar xvf "$1" -C "$2" >/dev/null || err "extraction failed" 35 | ;; 36 | zip) 37 | unzip -d "$2" -- "$1" >/dev/null || err "extraction failed" 38 | ;; 39 | x-rar) 40 | unrar x "$1" "$2" >/dev/null || err "extraction failed" 41 | ;; 42 | x-7z-compressed) 43 | 7z x -o"$2" "$1" >/dev/null || err "extraction failed" 44 | ;; 45 | x-xz) 46 | xzcat "$1" | tar xvf - -C "$2" >/dev/null || err "extraction failed" 47 | ;; 48 | *) 49 | err "%s: unknown archive file" 50 | ;; 51 | esac 52 | 53 | printf "%s -> %s\n" "$1" "$2" 54 | -------------------------------------------------------------------------------- /unpack.1: -------------------------------------------------------------------------------- 1 | .Dd September 26, 2022 2 | .Dt UNPACK 1 3 | .Os 4 | .Sh NAME 5 | .Nm unpack 6 | .Nd unarchive archive files 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Ar file 10 | .Op Ar directory 11 | .Sh DESCRIPTION 12 | .Nm 13 | extracts the contents of an archive file such as .zip or .tar.gz in the 14 | current directory or in the specified directory 15 | .PP 16 | The mimetypes of the supported archive file formats are the following: 17 | .Bl -bullet -compact 18 | .It 19 | application/gzip (.tar.gz) 20 | .It 21 | application/x-tar (.tar) 22 | .It 23 | application/zip (.zip) 24 | .It 25 | application/vnd.rar (.rar) 26 | .It 27 | application/x-7z-compressed (.7z) 28 | .El 29 | .Sh EXAMPLES 30 | Extract the content of file.zip into the home directory: 31 | .Bd -literal -offset indent 32 | $ unpack file.zip $HOME 33 | .Ed 34 | .Sh SEE ALSO 35 | .Xr tar 1 , 36 | .Xr gzip 1 , 37 | .Xr unzip 1 , 38 | .Xr unrar 1 , 39 | .Xr 7z 1 40 | -------------------------------------------------------------------------------- /untrash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # untrash: restore files from trash 3 | # this file in public domain 4 | 5 | ret=0 6 | for file 7 | do 8 | basename="$(basename "$file")" 9 | filesdir="$(dirname "$file")" 10 | infofile="$filesdir/../info/$basename.trashinfo" 11 | 12 | if ! test -e "$file" 13 | then 14 | printf "trash: %s: no such file or directory\n" "$file" >&2 15 | ret=1 16 | elif ! test -f "$infofile" 17 | then 18 | printf "trash: %s: cannot find original directory\n" "$basename" >&2 19 | ret=1 20 | elif origfile="$(awk -F= '/Path=/ {print $NF ; exit}' <"$infofile")" && test -e "$origfile" 21 | then 22 | printf "trash: %s: file already exists on original directory\n" "$basename" >&2 23 | ret=1 24 | elif ! mv -- "$file" "$origfile" 2>/dev/null 25 | then 26 | printf "trash: %s: cannot move file to its original directory\n" "$basename" >&2 27 | ret=1 28 | elif ! rm -- "$infofile" 29 | then 30 | printf "trash: %s: cannot clean file from trash\n" "$basename" >&2 31 | ret=1 32 | fi 33 | done 34 | exit "$ret" 35 | --------------------------------------------------------------------------------