├── preview ├── list.png ├── restore.png └── verbose.png ├── LICENSE ├── README.md └── bin └── brash /preview/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakariaGatter/brash/HEAD/preview/list.png -------------------------------------------------------------------------------- /preview/restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakariaGatter/brash/HEAD/preview/restore.png -------------------------------------------------------------------------------- /preview/verbose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakariaGatter/brash/HEAD/preview/verbose.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zakaria Barkouk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brash V0.1.2 2 | 3 | ## Table of Contents 4 | 5 | - [Why Brash](#why-brash) 6 | - [About](#about) 7 | - [Quick Start](#quick-start) 8 | - [Using Brash](#using-brash) 9 | - [Examples](#examples) 10 | - [Help](#help-needed) 11 | - [License](#License) 12 | 13 | ## Why Brash 14 | 15 | Well, why not. As you see its similar to [Trash_cli](https://github.com/andreafrancia/trash-cli). Unlike Trash_cli, [Brash] don't Depends on any Python libs just Bash Scripting. 16 | 17 | So why not ? 18 | 19 | ## About 20 | 21 | [Brash] Cli Trash Manager full in Bash 22 | 23 | [Brash] allows you to... 24 | 25 | * Delete files and folders, even the ones start with "-" 26 | * List out Trash contant 27 | * Clean Trash 28 | * Restore files from trash 29 | * Nice progress viewer of what happened 30 | * Use regular expressions 31 | 32 | [Brash] can automatically... 33 | 34 | * Rename existing files during Delete or Restore 35 | * Create a trash Folder in Removable Devices 36 | 37 | ## Quick Start 38 | 39 | * Installation requires : 40 | * [Dialog](https://invisible-island.net/dialog/) for Restore Multi select Dialog 41 | * [Coreutils](https://www.gnu.org/software/coreutils) for Everything else 42 | 43 | `Probably you just need Dialog` 44 | 45 | * Set up [Brash]: 46 | 47 | ``` bash 48 | git clone https://github.com/zakariaGatter/brash.git ~/brash 49 | cd ~/brash 50 | git checkout tags/v0.1.2 51 | mkdir -p ~/.local/bin 52 | cp bin/brash ~/.local/bin 53 | chmod +x ~/.local/bin/brash 54 | ``` 55 | 56 | ## Using Brash 57 | 58 | ``` 59 | Brash-0.1.2: Cli Trash Manager in Pure Bash 60 | USAGE: brash [OPTIONS] ... files ... 61 | 62 | OPTIONS: 63 | -d Move to Trash the giving files 64 | -r Restore Files and Directories from Trash 65 | -l List of Deleted files 66 | -c Clean Trash Bin 67 | --date Sort the Resoult by Date 68 | --file Sort the Resoult by file name 69 | -s Calculate Trash size 70 | -i Ask before every remove 71 | -I Ask once before removing more than three files 72 | -v Explaine what been done 73 | -h Display this help dialog 74 | -V Display Version 75 | 76 | NOTE: 77 | Format for '--date' option is (YYYY-MM-DD) or human readable date string 78 | See: 'man date' for more information. 79 | 80 | Options 81 | (--date/--file) options work only with -c,-l,-r Main Options 82 | ``` 83 | 84 | ## Preview 85 | ![List](./preview/list.png) 86 | ![Verbose](./preview/verbose.png) 87 | ![Restore](./preview/restore.png) 88 | 89 | 90 | ## Examples 91 | 92 | * Delete file or Directory (Add '-v' for verbose) 93 | ``` 94 | $ brash -d EX 95 | $ brash -d EX* 96 | $ brash -d EX/* 97 | $ brash -dv EX/[A-Z]* 98 | ``` 99 | 100 | * Restore file from Trash (Add '-v' for verbose) 101 | ``` 102 | $ brash -r 103 | $ brash -r --date "2022-05-05" 104 | $ brash -r --file vimrc.bk viminfo init.lua 105 | ``` 106 | 107 | * Show list of files in trash 108 | ``` 109 | $ brash -l 110 | $ brash -l --date "yesterday" 111 | $ brash -l --file code[1-9].py 112 | ``` 113 | 114 | * Clean Trash (Delete permanently) 115 | ``` 116 | $ brash -c 117 | $ brash -c --date "week ago" 118 | $ brash -c --file *.db 119 | ``` 120 | 121 | * Show Trash size 122 | ``` 123 | $ brash -s 124 | ``` 125 | 126 | ## Help Needed 127 | * Create a man page for [Brash] 128 | 129 | ## License 130 | 131 | MIT License 132 | Copyright (c) 2019 Zakaria Barkouk 133 | The above copyright notice and this permission notice shall be included in all 134 | copies or substantial portions of the Software. 135 | 136 | [Brash]:http://github.com/zakariagatter/brash 137 | -------------------------------------------------------------------------------- /bin/brash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #-------------# 4 | # SCRIPT NAME # 5 | #-------------# 6 | declare -r name="${0##*/}" 7 | declare -r VERSION="0.1.2" 8 | 9 | #-----------------------------------------# 10 | # BEFORE START CHECK IF DIALOG IS INSTALL # 11 | #-----------------------------------------# 12 | if ! command -v dialog &> /dev/null ; then 13 | printf "%b\n" "${name^}: Needs 'dialog' to use Restore and Clean option." >&2 14 | exit 2 15 | fi 16 | 17 | #-------------------# 18 | # GENERAL VARIABLES # 19 | #-------------------# 20 | declare -A options 21 | declare -u Answer 22 | declare -i error_count=0 23 | 24 | #---------------------# 25 | # ENABLE BASH OPTIONS # 26 | #---------------------# 27 | shopt -s dotglob extglob nullglob 28 | 29 | #---------------------------# 30 | # GET SYSTEM PARTITION UUID # 31 | #---------------------------# 32 | while read -ra line ; do 33 | [ "${line[0]%=*}" = "UUID" ] && PUUID+=( "${line[0]#*=}" ) 34 | done < /etc/fstab 35 | 36 | #----------------# 37 | # GET TRASH PATH # 38 | #----------------# 39 | while read -ra line ;do 40 | uuid=${line[0]} 41 | type=${line[1]} 42 | unset line[0] line[1] 43 | mount=${line[@]} 44 | 45 | if [ "$type" = "part" ]; then 46 | if ! [[ $uuid =~ ${PUUID[@]} ]]; then 47 | [[ $mount =~ $PWD ]] && TRASH_PATH="$mount" 48 | fi 49 | fi 50 | done< <(lsblk -ln -o uuid,type,mountpoint) 51 | unset PUUID uuid type mount line 52 | 53 | #-----------------# 54 | # TRASH DIRECTORY # 55 | #-----------------# 56 | [ "$TRASH_PATH" ] || TRASH_PATH="${XDG_DATA_HOME:-$HOME/.local/share}" 57 | TRASH_DIR="$TRASH_PATH/Trash" 58 | TRASH_INFO="$TRASH_DIR/info" 59 | TRASH_FILE="$TRASH_DIR/files" 60 | 61 | #------------------------# 62 | # CHECK FOR DIR EXISTING # 63 | #------------------------# 64 | [ -d "$TRASH_INFO" ] || mkdir -p "$TRASH_INFO" 65 | [ -d "$TRASH_FILE" ] || mkdir -p "$TRASH_FILE" 66 | 67 | #-----------------------------------# 68 | # GET LIST OF TRASH FILES AND INFOS # 69 | #-----------------------------------# 70 | TrashFiles=( $TRASH_FILE/* ) 71 | TrashInfos=( $TRASH_INFO/* ) 72 | 73 | #---------------------# 74 | # ERROR MSG WITH EXIT # 75 | #---------------------# 76 | die(){ 77 | printf "%s: %b\n" "${name^}" "$@\nTry '$name -h' for more information." >&2 78 | exit 2 79 | } 80 | 81 | #-----------# 82 | # ERROR MSG # 83 | #-----------# 84 | ero(){ 85 | printf "%s: %b\n" "${name^}" "$@" >&2 86 | (( error_count++ )) 87 | } 88 | 89 | #------------# 90 | # SIMPLE MSG # 91 | #------------# 92 | msg(){ 93 | printf "%s: %b\n" "${name^}" "$@" 94 | } 95 | 96 | #---------------------# 97 | # MOVE TO TRASH FILES # 98 | #---------------------# 99 | delete_func(){ 100 | #check for I option 101 | if [ "${options[I]}" ] && [ -z "${options[i]}" ]; then 102 | if [ ${#ListFiles[@]} -gt 3 ]; then 103 | printf "%s: %b" "${name^}" "Remove ${#ListFiles[*]} arguments recursively [Y/n]?" 104 | read -n1 -s Answer 105 | [ "$Answer" = "Y" ] || return 0 106 | fi 107 | fi 108 | 109 | for file in "${ListFiles[@]}"; do 110 | read full < <(realpath -s -- "$file") 111 | FilePath="${full%/*}" 112 | FileName="${full##*/}" 113 | 114 | # check the giving file is exist 115 | if ! [[ -e "$file" ]]; then 116 | ero "'$file' No such file or directory." 117 | continue 118 | # check if you have the permission to manage it 119 | elif ! [[ -w "$file" ]]; then 120 | ero "Can not Delete '$file' Permission denied" 121 | continue 122 | fi 123 | 124 | # check for i option 125 | if [ "${options[i]}" ] && [ -z "${options[I]}" ]; then 126 | printf "%s: %b\r" "${name^}" "Remove '$FileName' arguments recursively [Y/n]?" 127 | read -n1 -s Answer 128 | [ "$Answer" = "Y" ] || continue 129 | fi 130 | 131 | # if this a link manage it as so 132 | if [ -L "$file" ]; then 133 | unlink -- "$file" 134 | [ "${options[v]}" ] && msg "Unlinked '$file'" 135 | continue 136 | fi 137 | 138 | # check for name in Trash 139 | while [ -e "$TRASH_FILE/$FileName" ]; do 140 | FileName="${FileName}_" 141 | done 142 | 143 | # move file to Trash 144 | if (mv -f -- "$full" $TRASH_FILE &> /dev/null); then 145 | printf "[Trash Info]\n" > "$TRASH_INFO/$FileName.trashinfo" 146 | printf "DeletionDate=%(%F)TT%(%T)T\n" >> "$TRASH_INFO/$FileName.trashinfo" 147 | printf "PATH=%s\n" "$full" >> "$TRASH_INFO/$FileName.trashinfo" 148 | [ "${options[v]}" ] && msg "Removed '$file'" 149 | else 150 | ero "[Error] Deleting '$file'" 151 | fi 152 | done 153 | 154 | # exit with same number of errors 155 | [ $error_count -ne 0 ] && exit $error_count 156 | 157 | unset file full FilePath FileName 158 | } 159 | 160 | #------------------# 161 | # LIST TRASH FILES # 162 | #------------------# 163 | list_func(){ 164 | [ "${TrashInfos[*]}" ] || printf "%s\n" "Empty Trash, No files to List" >&2 165 | 166 | if [ "$DateList" ]; then 167 | (date --date="$DateList" +%F &> /dev/null) || die "invalid date '$DateList'." 168 | 169 | read DateCheck < <(date --date="$DateList" +%F) 170 | msg "List Trash files By Date." 171 | 172 | for item in "${TrashInfos[@]}"; do 173 | while read -r line; do 174 | [ "${line%%=*}" = "DeletionDate" ] && date=${line#*=} 175 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 176 | done < "$item" 177 | [[ ${date/T/ } =~ $DateCheck ]] && printf "[%s] %s\n" "${date/T/ }" "$file" 178 | unset file date 179 | done 180 | 181 | elif [ "$FileList" ]; then 182 | msg "List Trash files By Name. " 183 | 184 | for opt in "${ListFiles[@]}"; do 185 | for item in "${TrashInfos[@]}"; do 186 | while read -r line; do 187 | [ "${line%%=*}" = "DeletionDate" ] && date=${line#*=} 188 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 189 | done < "$item" 190 | [[ $file =~ $opt ]] && printf "[%s] %s\n" "${date/T/ }" "$file" 191 | unset file date 192 | done 193 | done 194 | 195 | elif [ "$empty" ]; then 196 | msg "List All Trash files" 197 | 198 | for item in "${TrashInfos[@]}"; do 199 | while read -r line; do 200 | [ "${line%%=*}" = "DeletionDate" ] && date=${line#*=} 201 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 202 | done < "$item" 203 | printf "[%s] %s\n" "${date/T/ }" "$file" 204 | unset file date 205 | done 206 | 207 | fi 208 | 209 | unset line empty DateList DateCheck item FileList opt 210 | } 211 | 212 | #-------------# 213 | # CLEAN TRASH # 214 | #-------------# 215 | clean_func(){ 216 | [ "${TrashInfos[*]}" ] || printf "%s\n" "Empty Trash, No files or Directories to Clean" >&2 217 | 218 | count=0 219 | 220 | if [ "$DateClean" ]; then 221 | (date --date="$DateClean" +%F &> /dev/null) || die "invalid date '$DateClean'." 222 | 223 | read DateCheck < <(date --date="$DateClean" +%F) 224 | 225 | for item in "${TrashInfos[@]}"; do 226 | while read -r line; do 227 | [ "${line%%=*}" = "DeletionDate" ] && date=${line#*=} 228 | done < "$item" 229 | if [[ ${date/T} =~ $DateCheck ]]; then 230 | file="${item##*/}" 231 | rm -rf "$item" "$TRASH_FILE/${file%.*}" 232 | ((count++)) 233 | printf "Cleaning: %s Item(s) By Date\r" "$count" "$DateClean" 234 | sleep 0.2 235 | fi 236 | done 237 | elif [ "$FileClean" ]; then 238 | for opt in "${ListFiles[@]}"; do 239 | for item in "${TrashInfos[@]}"; do 240 | while read -r line; do 241 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 242 | done < "$item" 243 | if [[ $file =~ $opt ]]; then 244 | file="${item##*/}" 245 | rm -rf "$item" "$TRASH_FILE/${file%.*}" 246 | ((count++)) 247 | printf "Cleaning: %s Item(s) by File\r" "$count" 248 | sleep 0.2 249 | fi 250 | done 251 | done 252 | elif [ "$empty" ]; then 253 | for item in "${TrashFiles[@]}" ;do 254 | rm -rf "$item" "$TRASH_INFO/${item##*/}.trashinfo" 255 | ((count++)) 256 | printf "Cleaning: %s Item(s)\r" "$count" 257 | sleep 0.2 258 | done 259 | fi 260 | printf "\n" 261 | 262 | unset count DateClean DateCheck item line date file FileClean opt empty 263 | } 264 | 265 | #-----------------------# 266 | # RESTORE DELETED FILES # 267 | #-----------------------# 268 | restore_func(){ 269 | [ "${TrashInfos[*]}" ] || die "Empty Trash, No files to Restore" 270 | 271 | if [ "$DateRestore" ]; then 272 | (date --date="$DateList" +%F &> /dev/null) || die "invalid date '$DateList'." 273 | 274 | read DateCheck < <(date --date="$DateList" +%F) 275 | 276 | for item in "${TrashInfos[@]}"; do 277 | while read -r line; do 278 | [ "${line%%=*}" = "DeletionDate" ] && date=${line#*=} 279 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 280 | done < "$item" 281 | if [[ ${date/T/ } =~ $DateCheck ]]; then 282 | while [ -e "$file" ]; do file="${file}_" ;done 283 | 284 | item="${item##*/}" 285 | item="${item%.*}" 286 | mv -f "$TRASH_FILE/$item" "$file" 287 | rm "$TRASH_INFO/$item.trashinfo" 288 | [ "${options[v]}" ] && msg "Restored '$file'" 289 | fi 290 | unset file date 291 | done 292 | 293 | elif [ "$FileRestore" ]; then 294 | for opt in "${ListFiles[@]}"; do 295 | for item in "${TrashInfos[@]}"; do 296 | while read -r line; do 297 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 298 | done < "$item" 299 | if [[ $file =~ $opt ]]; then 300 | while [ -e "$file" ]; do file="${file}_" ;done 301 | 302 | item="${item##*/}" 303 | item="${item%.*}" 304 | mv -f "$TRASH_FILE/$item" "$file" 305 | rm "$TRASH_INFO/$item.trashinfo" 306 | [ "${options[v]}" ] && msg "Restored '$file'" 307 | fi 308 | unset file date 309 | done 310 | done 311 | elif [ "$empty" ]; then 312 | read LINES COLUMNS < <(stty size) 313 | read -a resoult < <(eval "dialog --stdout --title '${name^} Restore' --checklist 'Pick Files to restore (Accept Multiple) :' $LINES $COLUMNS $(($LINES - 8)) $(ListItems)") 314 | 315 | for item in ${resoult[@]}; do 316 | while read line; do 317 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 318 | done < $TRASH_INFO/$item.trashinfo 319 | 320 | while [ -e "$file" ]; do file="${file}_" ;done 321 | 322 | mv -f "$TRASH_FILE/$item" "$file" 323 | rm "$TRASH_INFO/$item.trashinfo" 324 | [ "${options[v]}" ] && msg "Restored '$file'" 325 | done 326 | fi 327 | 328 | unset line empty DateList DateCheck item FileList opt 329 | } 330 | 331 | #-----------------------------------------# 332 | # LIST TRASH FILE INFO FOR RESTORE SELECT # 333 | #-----------------------------------------# 334 | ListItems(){ 335 | for item in "${TrashInfos[@]}"; do 336 | while read line; do 337 | [ "${line%%=*}" = "DeletionDate" ] && date=${line#*=} 338 | [ "${line%%=*}" = "PATH" ] && file=${line#*=} 339 | done < "$item" 340 | item="${item##*/}" 341 | printf "'%s' '%s' OFF " "${item%.*}" "[${date/T/ }] ${file/$HOME/\~}" 342 | done 343 | } 344 | 345 | 346 | #----------------# 347 | # GET TRASH SIZE # 348 | #----------------# 349 | size_func(){ 350 | [ "${TrashInfos[*]}" ] || printf "%s\n" "Empty Trash, No files Directories to Weight" >&2 351 | 352 | count=0 353 | 354 | for item in "${TrashFiles[@]}"; do 355 | ((count++)) 356 | read -r size < <(du -hcs $TRASH_FILE | awk '/total$/{print $1}') 357 | printf "Size [%s], Files [%s]\r" "$size" "$count" 358 | sleep 0.1 359 | done 360 | printf "\n" 361 | 362 | unset item count size 363 | } 364 | 365 | #-------------# 366 | # HELP DIALOG # 367 | #-------------# 368 | help_func(){ 369 | while read ; do 370 | printf "%s\n" "$REPLY" 371 | done <<-HELP 372 | ${name^}-$VERSION: Cli Trash Manager in Pure Bash 373 | USAGE: $name [OPTIONS] ... files ... 374 | 375 | OPTIONS: 376 | -d Move to Trash the giving files 377 | -r Restore Files and Directories from Trash 378 | -l List of Deleted files 379 | -c Clean Trash Bin 380 | --date Sort the Resoult by Date 381 | --file Sort the Resoult by file name 382 | -s Calculate Trash size 383 | -i Ask before every remove 384 | -I Ask once before removing more than three files 385 | -v Explaine what been done 386 | -h Display this help dialog 387 | -V Display Version 388 | 389 | NOTE: 390 | Format for '--date' option is (YYYY-MM-DD) or human readable date string 391 | See: 'man date' for more information. 392 | 393 | Options 394 | (--date/--file) options work only with -c,-l,-r Main Options 395 | 396 | HELP 397 | } 398 | 399 | #---------------# 400 | # MAIN FUNCTION # 401 | #---------------# 402 | main_func(){ 403 | while getopts ':dlrcsiIvhV' OPT ; do 404 | case "$OPT" in 405 | d ) options[d]=true 406 | if [[ $2 =~ ^-[dlrcsiIvhV]$ ]] || [[ -z "$2" ]] ; then 407 | continue 408 | fi 409 | ;; 410 | l ) options[l]=true 411 | if [ "$2" = "--date" ]; then 412 | [ "$3" ] || die "Option require a argument -- 'date'." 413 | DateList="$3" && shift 414 | elif [ "$2" = "--file" ]; then 415 | [ "$3" ] || die "Option require a argument -- 'file'." 416 | FileList=true && shift 417 | if [[ $2 =~ ^-[dlrcsiIvhV]$ ]] || [[ -z "$2" ]]; then 418 | continue 419 | fi 420 | else 421 | empty=true 422 | fi 423 | ;; 424 | r ) options[r]=true 425 | if [ "$2" = "--date" ]; then 426 | [ "$3" ] || die "Option require a argument -- 'date'." 427 | DateRestore="$3" && shift 428 | elif [ "$2" = "--file" ]; then 429 | [ "$3" ] || die "Option require a argument -- 'file'." 430 | FileRestore=true && shift 431 | if [[ $2 =~ ^-[dlrcsiIvhV]$ ]] || [[ -z "$2" ]]; then 432 | continue 433 | fi 434 | else 435 | empty=true 436 | fi 437 | ;; 438 | c ) options[c]=true 439 | if [ "$2" = "--date" ]; then 440 | [ "$3" ] || die "Option require a argument -- 'date'." 441 | DateClean="$3" && shift 442 | elif [ "$2" = "--file" ]; then 443 | [ "$3" ] || die "Option require a argument -- 'file'." 444 | FileClean=true && shift 445 | if [[ $2 =~ ^-[dlrcsiIvhV]$ ]] || [[ -z "$2" ]]; then 446 | continue 447 | fi 448 | else 449 | empty=true 450 | fi 451 | ;; 452 | s ) options[s]=true ;; 453 | i ) options[i]=true ;; 454 | I ) options[I]=true ;; 455 | v ) options[v]=true ;; 456 | h ) options[h]=true ;; 457 | V ) options[V]=true ;; 458 | : ) die "'-$OPTARG' Missing Operand." ;; 459 | ? ) die "Invalid option -- '-$OPTARG'." ;; 460 | esac 461 | shift $((OPTIND-1)) 462 | done 463 | 464 | for file in "$@"; do 465 | ListFiles+=( "$file" ) 466 | done 467 | } 468 | 469 | #-------------------# 470 | # RUN MAIN FUNCTION # 471 | #-------------------# 472 | main_func "$@" 473 | 474 | #-------------------# 475 | # EXEC USER OPTIONS # 476 | #-------------------# 477 | [ -z "${options[d]}" ] || delete_func 478 | [ -z "${options[l]}" ] || list_func 479 | [ -z "${options[r]}" ] || restore_func 480 | [ -z "${options[c]}" ] || clean_func 481 | [ -z "${options[s]}" ] || size_func 482 | [ -z "${options[h]}" ] || help_func 483 | [ -z "${options[V]}" ] || printf "%s\n" "${name^} Version: $VERSION (MIT Licence)" 484 | --------------------------------------------------------------------------------