Bash-Funk “filesystem” module
The following commands are available when this module is loaded:
- -abspath
- -cd-down
- -cd-hist
- -cd-up
- -count-words
- -du
- -extract
- -find-up
- -findfiles
- -ll
- -mkcd
- -modified
- -owner
- -realpath
- -sudo-append
- -sudo-write
- -tail-reverse
- -test-all-filesystem
License
SPDX-FileCopyrightText: © Vegard IT GmbH (https://vegardit.com)
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-abspath
Usage: -abspath [OPTION]... [PATH]
Prints the normalized path of the given path WITHOUT resolving symbolic links. The path is not required to exist.
Parameters:
PATH (default: '.')
The path to normalize.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
# use realpath if available
if hash realpath &>/dev/null; then
realpath -m $_PATH
# use python as last resort
else
python -c "import os
print(os.path.abspath('$_PATH'))"
fi
-cd-down
Usage: -cd-down [OPTION]... [START_AT] DIR_NAME
Jumps down in the tree of the current directory to the first sub directory found with the given name.
Parameters:
START_AT (default: '.')
The start directory.
DIR_NAME (required)
The name of the subdirectory to locate and cd into.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
local path=$(find $_START_AT -name "$_DIR_NAME" -type d -print -quit 2>/dev/null || true);
if [[ $path ]]; then
echo "$path"
cd $path
else
echo "-cd-down: $_DIR_NAME: No such directory"
return 1
fi
-cd-hist
Usage: -cd-hist [OPTION]... [STEPS_OR_DIRNAME]
Navigates back in the directory history which can be managed via pushd/popd/dirs and is automatically populated if the Bash Funk bash-prompt is installed.
Parameters:
STEPS_OR_DIRNAME
The name of the subdirectory to locate and cd into. If not specified a list of the last 20 entries is displayed.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
if [[ ! $_STEPS_OR_DIRNAME ]]; then
echo "Directory history:"
for (( __idx=2; __idx<${#DIRSTACK[*]}; __idx++ )); do
echo "$(( __idx - 1 )) cd ${DIRSTACK[$__idx]}"
[[ $__idx -eq 22 ]] && break || true
done
return 0
fi
if [[ $_STEPS_OR_DIRNAME == "-" ]]; then
cd - && return 0 || return 1
fi
if [[ $_STEPS_OR_DIRNAME =~ ^[0-9]+$ ]]; then
local path="${DIRSTACK[@]:$(( _STEPS_OR_DIRNAME + 1 )):1}"
echo "$path"
cd $path
else
local path
for path in "${DIRSTACK[@]}"; do
case "${path}" in
*"/"$_STEPS_OR_DIRNAME)
echo "$path"
cd "$path"
return 0;
;;
esac
done
echo "-cd-hist: $_STEPS_OR_DIRNAME: No such directory in history"
return 1
fi
-cd-up
Usage: -cd-up [OPTION]... [LEVEL_OR_PATTERN]
Navigates up in the current directory tree to the first parent directory found with the given namen or the given number of levels. Bash completion will auto-complete the names of the parent directories.
Parameters:
LEVEL_OR_PATTERN (default: '..')
The number of directories to navigate up in the directory tree or the glob pattern to find a matching directory.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
if [[ $_LEVEL_OR_PATTERN == ".." ]]; then
cd ..
return 0
fi
# check if value is numeric
if [[ $_LEVEL_OR_PATTERN =~ ^[0-9]+$ ]]; then
local path
for (( i = 0; i < _LEVEL_OR_PATTERN; i++ )); do
path="../$path"
done
echo "$path"
cd "$path"
else
local elem path=()
# read current path elements into array 'path'
IFS=/ read -r -a path <<< "$PWD"
# iterate reverse through the array and check for matching directory
for (( idx=${#path[@]}-2; idx>=0; idx-- )); do
case "${path[idx]}" in
$_LEVEL_OR_PATTERN)
# join the path
IFS="/" eval 'path="${path[*]:0:$((idx+1))}"'
echo "$path"
cd "$path"
return 0;
;;
esac
done
echo "-cd-up: $_LEVEL_OR_PATTERN: No such directory"
return 1
fi
-count-words
Usage: -count-words [OPTION]... WORD1 [WORD]...
Counts the number of occurences of the word(s) in the text read from stdin.
Parameters:
WORD (1 or more required)
The word to count.
Options:
-f, --file PATH (file)
Count the words in the given file instead of reading from stdin.
-s, --sort MODE (one of: [count,word])
Specifies how to sort the output.
-----------------------------
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
local sedCmds grepCmds
for word in "${_WORD[@]}"; do
sedCmds="s/$word/\n$word\n/g; $sedCmds"
grepCmds="$grepCmds -e $word"
done
if [[ $_file ]]; then
if [[ $_sort == "count" ]]; then
sed "$sedCmds" "$_file" | grep $grepCmds | sort | uniq -c | sort -r
else
sed "$sedCmds" "$_file" | grep $grepCmds | sort | uniq -c
fi
else
# check if stdin is opend on terminal (and thus not on a pipe)
if [[ -t 0 ]]; then
return 0
fi
if [[ $_sort == "count" ]]; then
cat /dev/fd/0 | sed "$sedCmds" | grep $grepCmds | sort | uniq -c | sort -r
else
cat /dev/fd/0 | sed "$sedCmds" | grep $grepCmds | sort | uniq -c
fi
fi
-du
Usage: -du [OPTION]... [PATH]...
Prints disk usage information.
Parameters:
PATH
The path to check.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
[[ ! $_PATH ]] && _PATH=(.) || true
du -s -h "${_PATH[@]}"
-extract
Usage: -extract [OPTION]... ARCHIVE [TO_DIR]
Extracts the given archive using the compatible extractor.
Parameters:
ARCHIVE (required, file)
The archive to extract.
TO_DIR
The target folder.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
if [[ $_TO_DIR ]]; then
local origPWD="$PWD"
mkdir "$_TO_DIR"
cd "$_TO_DIR"
fi
if [[ ! -w "$PWD" ]]; then
echo "-extract: Error: Path [$PWD] is not writeable."
return 1
fi
local tmpDir=$(mktemp -d -p "$PWD")
case "$_FILE" in
*.bz2) bunzip2 "$_ARCHIVE" ;;
*.gz) gunzip "$_ARCHIVE" ;;
*.rar) unrar x "$_ARCHIVE" ;;
*.tar) tar xvf "$_ARCHIVE" ;;
*.tbz2|*.tar.bz2) tar xvjf "$_ARCHIVE" ;;
*.tgz|*.tar.gz) tar xvzf "$_ARCHIVE" ;;
*.zip) unzip "$_ARCHIVE" ;;
*.Z) uncompress "$_ARCHIVE" ;;
*.7z) 7z x "$_ARCHIVE" ;;
*) echo "-extract: Error: Unsupported archive format '$_ARCHIVE'"; return 1 ;;
esac
if [[ $_TO_DIR ]]; then
cd "$origPWD"
fi
-find-up
Usage: -find-up [OPTION]... FILENAME
Traverses the directory upward to find the given file.
Parameters:
FILENAME (required)
The file or directory to find.
Options:
-t, --type TYPE (one of: [d,dir,f,file])
File type.
-----------------------------
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
local path=$PWD
while [[ $path ]]; do
case $_type in
d|dir) if [[ -d "$path/$_FILENAME" ]]; then echo "$path/$_FILENAME"; return; fi ;;
f|file) if [[ -f "$path/$_FILENAME" ]]; then echo "$path/$_FILENAME"; return; fi ;;
*) if [[ -e "$path/$_FILENAME" ]]; then echo "$path/$_FILENAME"; return; fi ;;
esac
path=${path%/*}
done
echo "-find-up: '$_FILENAME': No such file or directory"
return 1
-findfiles
Usage: -findfiles [OPTION]... [START_PATH] SEARCH_STRING
Recursively finds all files containing the given string and displays their path.
Parameters:
START_PATH (default: '.')
The path where to search.
SEARCH_STRING (required)
The string to search.
Options:
-l, --lines
Show matching lines of the files that contain the given string.
--maxdepth levels (integer: ?-?)
The maximum number of levels to descend into the directory tree below the starting-point.
--mindepth levels (integer: ?-?)
The level of directory tree below the starting-point where to start the search.
--name pattern
Name pattern.
-u, --unpack
Unpack supported archives (.zip, .jar, .war, .ear).
-v, --verbose
Prints additional information during command execution.
-----------------------------
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
if [[ ! -e "$_START_PATH" ]]; then
echo "-findfiles: Error: Path [$_START_PATH] does not exist."
return 1
fi
if [[ ! -r "$_START_PATH" ]]; then
echo "-findfiles: Error: Path [$_START_PATH] is not readable by user '$USER'."
return 1
fi
if [[ $_lines ]]; then
local grepCmd="grep -n"
else
local grepCmd="grep -l"
fi
local findOpts="-type f"
if [[ $_name ]]; then findOpts="$findOpts -name $_name"; fi
if [[ $_maxdepth ]]; then findOpts="$findOpts -maxdepth $_maxdepth"; fi
if [[ $_mindepth ]]; then findOpts="$findOpts -mindepth $_mindepth"; fi
# turn off verbose if part of pipe or subshell
[[ ! $__interactive ]] && _verbose= || true
if [[ $_verbose ]]; then
if hash tput &>/dev/null; then
cols=$(tput cols)
else
cols=$(stty size| cut -d' ' -f 2)
fi
find "$_START_PATH" $findOpts 2>/dev/null | while read file; do
local message="Scanning $file ..."
echo -en "\033[s${message:0:$cols}"
if [[ $_unpack && ( $file == *.zip || $file == *.jar || $file == *.ear || $file == *.war ) ]]; then
if unzip -p "$file" | LC_ALL=C $grepCmd "$_SEARCH_STRING" "$file" 2>&1 >/dev/null; then
echo -e "\033[u\033[K$file" || echo -e "$file"
else
echo -en "\033[u\033[K"
fi
else
if LC_ALL=C $grepCmd "$_SEARCH_STRING" "$file" 2>&1 >/dev/null; then
echo -e "\033[u\033[K$file" || echo -e "$file"
else
echo -en "\033[u\033[K"
fi
fi
done
else
if [[ $_unpack ]]; then
find "$_START_PATH" $findOpts 2>/dev/null | while read file; do
if [[ $file == *.zip || $file == *.jar || $file == *.ear || $file == *.war ]]; then
unzip -p "$file" | LC_ALL=C $grepCmd "$_SEARCH_STRING" "$file" 2>/dev/null || true
else
LC_ALL=C $grepCmd "$_SEARCH_STRING" "$file" 2>/dev/null
fi
done
else
find "$_START_PATH" $findOpts -print0 | LC_ALL=C xargs -r -0 -P2 $grepCmd "$_SEARCH_STRING" 2>/dev/null
fi
fi
-ll
Usage: -ll [OPTION]... [PATH]...
Alternative version of 'ls -lt' that prints directories (and sym-links to directories) before files and hidden entries before non-hidden entries.
Parameters:
PATH
The path to list.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
[[ ! $_PATH ]] && _PATH=(.) || true
local _ls="command ls -lAph \"${_PATH[@]}\""
[[ $OSTYPE == "darwin"* ]] && _ls="$_ls -G" || _ls="$_ls -I lost+found --color=always"
eval $_ls | awk '
BEGIN { dotDirs = ""; dirs = ""; dotFiles = ""; files = "" }
/^total/ { total = $0 } # capture total line
/^d[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m)?[.].+/ { dotDirs = dotDirs "\n" $0 }; # capture hidden directories
/^d[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m[^.]|[^\033^.])/ { dirs = dirs "\n" $0 }; # capture normal directories
/^l[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m)?[.].+[\/]/ { dotDirs = dotDirs "\n" $0 }; # capture hidden sym-links to directories
/^l[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m[^.]|[^\033^.]).*[\/]$/ { dirs = dirs "\n" $0 }; # capture normal sym-links to directories
/^-[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m)?[.].+/ { dotFiles = dotFiles "\n" $0 }; # capture hidden files
/^-[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m[^.]|[^\033^.])/ { files = files "\n" $0 }; # capture normal files
/^l[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m)?[.].+[^\/]/ { dotFiles = dotFiles "\n" $0 }; # capture hidden sym-links to files
/^l[rwxXst+-]+.? .* ([0-9][0-9][0-9][0-9]|[0-9]+:[0-9]+) (\033\[[0-9;]+m[^.]|[^\033^.]).*[^\/]$/ { files = files "\n" $0 }; # capture normal sym-links to files
END { print total dotDirs dirs dotFiles files }'
-mkcd
Usage: -mkcd [OPTION]... PATH
Creates a directory and changes into it.
Parameters:
PATH (required)
The path to create.
Options:
-m, --mode MODE (pattern: "[0-7]{3}")
The file mode for the new directory.
-p, --parents
Automatically create missing parent directories.
-v, --verbose
Prints additional information during command execution.
-----------------------------
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
local mkdirOpts
[[ $_mode ]] && mkdirOpts="$mkdirOpts -m $_mode" || true
[[ $_parents ]] && mkdirOpts="$mkdirOpts -p" || true
[[ $_verbose ]] && mkdirOpts="$mkdirOpts -v" || true
mkdir "$_PATH" && cd "$_PATH"
-modified
Usage: -modified [OPTION]... [PATH]
Prints the modification timestamp of the given file or directory.
Parameters:
PATH (default: '.', path)
The file or directory to check.
Options:
-f, --format FORMAT (one of: [locale,iso8601,human])
Prints the timestamp in the given format.
-----------------------------
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
local _format=${_format:-timestamp}
case $_format in
human) find "$_PATH" -maxdepth 0 -printf "%TY.%Tm.%Td %TT %TZ\n" ;;
locale) find "$_PATH" -maxdepth 0 -printf "%Tc\n" ;;
timestamp)
# use stat if available
if hash stat &>/dev/null; then
stat -c %Y "$_PATH"
# use perl if available
elif hash perl &>/dev/null; then
perl << EOF
use File::stat;
print stat("$_PATH")->mtime, "\n"
EOF
# use python as last resort
else
python -c "import os, pwd
print(str(int(os.path.getmtime('$_PATH'))))"
fi
;;
iso8601)
# use stat if available
if hash stat &>/dev/null; then
local timestamp=$(stat -c %Y "$_PATH")
date --iso-8601=seconds -d@$timestamp
# use perl if available
elif hash perl &>/dev/null; then
local timestamp=$(perl << EOF
use File::stat;
print stat("$_PATH")->mtime, "\n"
EOF
)
date --iso-8601=seconds -d@$timestamp
# use python as last resort
else
python -c "import os, pwd, datetime, time
print(datetime.datetime.fromtimestamp(int(os.path.getmtime('$_PATH'))).isoformat() + time.strftime('%z'))"
fi
;;
esac
-owner
Usage: -owner [OPTION]... [PATH]
Prints the owner of the given file or directory.
Parameters:
PATH (default: '.', path)
The file or directory to check.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
# use stat if available
if hash stat &>/dev/null; then
echo $(stat -c %U "$_PATH")
# use perl if available
elif hash perl &>/dev/null; then
perl << EOF
use File::stat;
print getpwuid(stat("$_PATH")->uid), "\n"
EOF
# use python as last resort
else
python -c "import os, pwd
print(pwd.getpwuid(os.stat('$_PATH').st_uid).pw_name)"
fi
-realpath
Usage: -realpath [OPTION]... [PATH]
Prints the normalized path of the given path resolving any symbolic links. The path is not required to exist.
Parameters:
PATH (default: '.', path)
The path to normalize.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
# use readlink if available
if hash readlink &>/dev/null; then
readlink -m "$_PATH"
# use perl if available
elif hash perl &>/dev/null; then
perl << EOF
use Cwd 'abs_path';
print abs_path('$_PATH'), "\n"
EOF
# use python as last resort
else
python -c "import os
print(os.path.realpath('$_PATH'))"
fi
-sudo-append
Usage: -sudo-append [OPTION]... FILE_PATH CONTENT
Creates a file with the given content.
Requirements:
+ Sudo 'tee -a' is required.
Parameters:
FILE_PATH (required)
The path to the file to write.
CONTENT (required)
The content to append to the file.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Examples:
$ -sudo-append /tmp/testfile.cfg 'foo=bar'
Appending to \[/tmp/testfile.cfg\]...
Implementation:
echo "Appending to [$_FILE_PATH]..."
echo "$_CONTENT" | sudo tee -a "$_FILE_PATH" > /dev/null
-sudo-write
Usage: -sudo-write [OPTION]... FILE_PATH OWNER CONTENT
Creates a file with the given content.
Requirements:
+ Sudo 'sh -c' is required.
+ Sudo 'sh chown' is required.
Parameters:
FILE_PATH (required)
The path to the file to write.
OWNER (required)
The owner and group to set.
CONTENT (required)
The content to write.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Examples:
$ -sudo-write /tmp/testfile.cfg $USER:$GROUP 'foo=bar'
Writing \[/tmp/testfile.cfg\]...
Implementation:
echo "Writing [$_FILE_PATH]..."
sudo sh -c "echo '$_CONTENT' > '$_FILE_PATH'" && sudo chown "$_OWNER" "$_FILE_PATH"
-tail-reverse
Usage: -tail-reverse [OPTION]... FILE
Prints the last N lines of the given text file in reverse order.
Parameters:
FILE (required, file)
Path to the file.
Options:
-n, --lines N (integer: ?-?)
The maximum number of lines to output.
-u, --unique
Don't print duplicates.
-----------------------------
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
if [[ $_unique ]]; then
if [[ $_lines ]]; then
awk "{lines[len++]=\$0} END {for(i=len-1;i>=0;i--) if (occurrences[lines[i]]++ == 0) { print lines[i]; count++; if (count>=$_lines) break}}" $_FILE
else
awk "{lines[len++]=\$0} END {for(i=len-1;i>=0;i--) if (occurrences[lines[i]]++ == 0) print lines[i]}" $_FILE
fi
else
if [[ $_lines ]]; then
awk "{lines[len++]=\$0} END {for(i=len-1;len-i<=$_lines;i--) print lines[i]}" $_FILE
else
awk "{lines[len++]=\$0} END {for(i=len-1;i>=0;i--) print lines[i]}" $_FILE
fi
fi
-test-all-filesystem
Usage: -test-all-filesystem [OPTION]...
Performs a selftest of all functions of this module by executing each function with option '--selftest'.
Options:
--help
Prints this help.
--tracecmd
Enables bash debug mode (set -x).
--selftest
Performs a self-test.
--
Terminates the option list.
Implementation:
-abspath --selftest && echo || return 1
-cd-down --selftest && echo || return 1
-cd-hist --selftest && echo || return 1
-cd-up --selftest && echo || return 1
-count-words --selftest && echo || return 1
-du --selftest && echo || return 1
-extract --selftest && echo || return 1
-find-up --selftest && echo || return 1
-findfiles --selftest && echo || return 1
-ll --selftest && echo || return 1
-mkcd --selftest && echo || return 1
-modified --selftest && echo || return 1
-owner --selftest && echo || return 1
-realpath --selftest && echo || return 1
-sudo-append --selftest && echo || return 1
-sudo-write --selftest && echo || return 1
-tail-reverse --selftest && echo || return 1