Last active
April 6, 2025 13:46
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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