!/usr/bin/env bash
Linux-only Unicode tree with sizes and total files
print_tree() {
local dir=”$1″
local prefix=”${2:-}”
local maxdepth=”${3:-0}” # 0 = unlimited
local depth=”${4:-1}”
# Skip if not a directory
[[ ! -d "$dir" ]] && return
[[ $maxdepth -ne 0 && $depth -gt $maxdepth ]] && return
echo "${prefix}${dir##*/}/"
shopt -s nullglob
# Directories first
local dirs=()
for d in "$dir"/*; do
[[ -d "$d" && ! -L "$d" ]] && dirs+=("$d") # skip symlinks
done
IFS=$'n' dirs=($(printf "%sn" "${dirs[@]}" | sort))
for i in "${!dirs[@]}"; do
local d="${dirs[i]}"
local branch="├──"
[[ $i -eq $(( ${#dirs[@]} - 1 )) ]] && branch="└──"
local new_prefix="$prefix"
[[ $i -eq $(( ${#dirs[@]} - 1 )) ]] && new_prefix+=" " || new_prefix+="│ "
echo "${prefix}${branch} ${d##*/}/"
print_tree "$d" "$new_prefix" "$maxdepth" $((depth+1))
done
# Files next
local files=()
for f in "$dir"/*; do
[[ -f "$f" ]] && files+=("$f")
done
IFS=$'n' files=($(printf "%sn" "${files[@]}" | sort))
local file_count=0
for i in "${!files[@]}"; do
local f="${files[i]}"
local branch="├──"
[[ $i -eq $(( ${#files[@]} - 1 )) ]] && branch="└──"
# Get size safely
local size=$(stat -c "%s" "$f" 2>/dev/null || echo 0)
# Convert to human-readable
local hr_size
if [[ "$size" -lt 1024 ]]; then hr_size="${size}B"
elif [[ "$size" -lt 1048576 ]]; then hr_size="$((size/1024))K"
elif [[ "$size" -lt 1073741824 ]]; then hr_size="$((size/1024/1024))M"
else hr_size="$((size/1024/1024/1024))G"; fi
echo "${prefix}${branch} ${f##*/} [${hr_size}]"
((file_count++))
done
[[ $file_count -gt 0 ]] && echo "${prefix}└── Total files: $file_count"
shopt -u nullglob
}
Usage: print_tree
Example: unlimited depth from current directory
print_tree $ROOT “” 0