Skip to content

Instantly share code, notes, and snippets.

@mathmul
Last active April 6, 2025 13:46
Show Gist options
  • Save mathmul/25364f324db85e2657c5e3d9eec29d9b to your computer and use it in GitHub Desktop.
Save mathmul/25364f324db85e2657c5e3d9eec29d9b to your computer and use it in GitHub Desktop.
similar to 'll' or 'ls -la', but it finds nested files/directories as well
function llR() {
# Recursive ll
#
# Description:
# Lists files and directories similar to how 'ls -la' does. Differences:
# - lists files and directories recursively as well
# - lists full paths
# - accepts second argument as a comma separated list of dirs, that should not be explored deeper
# - '-h' flag gives human readable sizes same as with 'ls' command, but '-H' flag makes it more human readable, eg., '12K' vs '12 KB'
# flags
local humanClassic=false
local human=false
local printPwd=false
while [[ "$1" == -* ]]; do
case "$1" in
-h) humanClassic=true ;;
-H) human=true ;;
--pwd) printPwd=true ;;
esac
shift
done
# arguments
local dir="$1"
local exclude_arg="$2"
local excludes
if [[ -n "$exclude_arg" ]]; then
IFS=',' read -rA excludes <<< "$exclude_arg"
fi
# find all exclude directories
local skip_dirs=()
for ex in "${excludes[@]}"; do
while IFS= read -r match; do
skip_dirs+=("$match")
done < <(find "$dir" -type d -name "$ex" 2>/dev/null)
done
# process all files and directories; for excluded, skip contents and add /** to filename
local listed_skips=()
local all_output=()
while IFS= read -r item; do
local skip=false
local is_skipped_dir=false
for skip_dir in "${skip_dirs[@]}"; do
if [[ "$item" == "$skip_dir"* ]]; then
skip=true
[[ "$item" == "$skip_dir" ]] && is_skipped_dir=true
break
fi
done
if [[ $skip == false && -e "$item" ]]; then
all_output+=("$(stat -f "%Sp|%l|%Su|%Sg|%z|%Sm|%N" "$item")")
elif [[ "$is_skipped_dir" == true && -e "$item" ]]; then
all_output+=("$(stat -f "%Sp|%l|%Su|%Sg|%z|%Sm|%N/**" "$item")")
fi
done < <(find "$dir" -print 2>/dev/null)
# print current directory
if [[ $printPwd == true ]]; then
echo "$(pwd)"
fi
# format and display
printf '%s\n' "${all_output[@]}" | sort -t'|' -k7,7 | awk -F"|" -v human="$human" -v humanClassic="$humanClassic" '
BEGIN { BLUE = "\033[34m"; RESET = "\033[0m" }
{
"ls -ld " $7 " 2>/dev/null" | getline lsout
perm = substr(lsout, 1, 11)
if (match(perm, /@$/)) {
$1 = $1 "@"
}
size = $5
if (human == "true" || humanClassic == "true") {
sizes["K"] = 1024
sizes["M"] = 1024 * 1024
sizes["G"] = 1024 * 1024 * 1024
sizes["T"] = 1024 * 1024 * 1024 * 1024
prefix = " "
if (size >= sizes["T"] * 0.95) {
prefix = "T"
} else if (size >= sizes["G"] * 0.95) {
prefix = "G"
} else if (size >= sizes["M"] * 0.95) {
prefix = "M"
} else if (size >= 1000) {
prefix = "K"
}
if (prefix != " ") {
size = int((size * 10 + sizes[prefix] / 2) / sizes[prefix]) / 10
size = (size < 10) ? sprintf("%.1f", size) : sprintf("%.0f", size)
}
if (humanClassic == "true") {
if (prefix == " ") {
prefix = "B"
}
size = size prefix
} else {
size = size " " prefix "B"
}
}
color = ($1 ~ /^d/) ? BLUE : ""
reset = (color != "") ? RESET : ""
printf "%s%-11s %2s %-8s %-8s %8s %-20s %s%s\n", color, $1, $2, $3, $4, size, $6, $7, reset
}'
}
alias llr='llR -H --pwd . .git,.venv,__pycache__,node_modules,vendor,.next,dist,build,.idea,.DS_Store'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment