Skip to content

Instantly share code, notes, and snippets.

@hunandy14
Created May 29, 2024 10:11

Revisions

  1. hunandy14 created this gist May 29, 2024.
    229 changes: 229 additions & 0 deletions tree.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,229 @@
    #!/bin/bash

    # This script displays the directory tree structure, color-codes files and folders,
    # and optionally shows file sizes and hidden files.

    # Usage:
    # tree.sh [-a] [-s] <directory>
    # -a Show all files including hidden files
    # -s Show file sizes

    # Detailed Usage:
    # - `-a` or `--all`: Show all files, including hidden files.
    # - `-s` or `--size`: Display the size of each file.

    # Examples:
    # 1. Show the directory structure, including hidden files and file sizes:
    # tree.sh -a -s testdir/
    #
    # 2. Show the directory structure without hidden files and file sizes:
    # tree.sh testdir/
    #
    # 3. Show the directory structure with file sizes but without hidden files:
    # tree.sh -s testdir/



    # Function to format file size
    function format_size() {
    local size=$1
    local units=("B" "KB" "MB" "GB" "TB" "PB")
    local unit_index=0
    local decimal_places=3

    while (( $(echo "$size >= 1024" | bc -l) )); do
    size=$(echo "scale=$decimal_places; $size / 1024" | bc)
    unit_index=$(( unit_index + 1 ))
    done

    size=$(echo $size | awk '{printf "%.'$decimal_places'f", $0}')
    size=$(echo $size | sed 's/\.0*$//;s/\.\([0-9]*[1-9]\)0*$/.\1/')

    echo "$size ${units[$unit_index]}"
    }

    # Function to print the item name with color based on file type
    function print_item() {
    local item_name=$1
    shift
    local description=""
    local item_type=""

    while [ $# -gt 0 ]; do
    case $1 in
    --description=*|-d)
    if [[ "$1" == "--description="* ]]; then
    description="${1#*=}"
    shift
    else
    description="$2"
    shift 2
    fi
    ;;
    --type=*|-t)
    if [[ "$1" == "--type="* ]]; then
    item_type="${1#*=}"
    shift
    else
    item_type="$2"
    shift 2
    fi
    ;;
    *)
    echo "Unknown argument: $1"
    return 1
    ;;
    esac
    done

    local base_name=$(basename "$item_name")
    local extension="${base_name##*.}"
    local COLOR_RESET="\033[0m"
    local COLOR_FOLDER="\033[1;34m" # Blue color for folders
    local COLOR_EXEC="\033[1;32m" # Green color for executable files
    local COLOR_LINK="\033[1;36m" # Cyan color for links
    local COLOR_COMPRESSED="\033[1;31m" # Red color for compressed files
    local COLOR_IMAGE="\033[1;35m" # Magenta color for image files
    local COLOR_VIDEO="\033[1;33m" # Yellow color for video files
    local COLOR_AUDIO="\033[1;36m" # Cyan color for audio files
    local COLOR_FIFO="\033[1;33;40m" # Yellow background black text for FIFO
    local COLOR_DEVICE="\033[1;34;43m" # Yellow background blue text for devices
    local COLOR_SUID="\033[1;37;41m" # White background red text for SUID files
    local COLOR_SGID="\033[1;30;43m" # Black background yellow text for SGID files
    local COLOR_STICKY="\033[1;37;44m" # Blue background white text for sticky directories
    local COLOR_OW="\033[1;30;42m" # Green background black text for other-writable directories
    local COLOR_DEFAULT="\033[0m" # Default color for other files

    local colored_name
    if [ "$item_type" = "folder" ]; then
    colored_name="${COLOR_FOLDER}${base_name}/${COLOR_RESET}"
    else
    if [ -x "$item_name" ]; then
    colored_name="${COLOR_EXEC}${base_name}${COLOR_RESET}"
    elif [ -L "$item_name" ]; then
    colored_name="${COLOR_LINK}${base_name}${COLOR_RESET}"
    elif [[ "$extension" =~ ^(zip|tar|gz|bz2|xz|7z|rar)$ ]]; then
    colored_name="${COLOR_COMPRESSED}${base_name}${COLOR_RESET}"
    elif [[ "$extension" =~ ^(jpg|jpeg|png|gif|bmp|tiff|svg|ico)$ ]]; then
    colored_name="${COLOR_IMAGE}${base_name}${COLOR_RESET}"
    elif [[ "$extension" =~ ^(mp4|mkv|avi|mov|flv|wmv|webm)$ ]]; then
    colored_name="${COLOR_VIDEO}${base_name}${COLOR_RESET}"
    elif [[ "$extension" =~ ^(mp3|wav|ogg|flac|aac|m4a)$ ]]; then
    colored_name="${COLOR_AUDIO}${base_name}${COLOR_RESET}"
    elif [ -p "$item_name" ]; then
    colored_name="${COLOR_FIFO}${base_name}${COLOR_RESET}"
    elif [ -b "$item_name" ] || [ -c "$item_name" ]; then
    colored_name="${COLOR_DEVICE}${base_name}${COLOR_RESET}"
    elif [ -u "$item_name" ]; then
    colored_name="${COLOR_SUID}${base_name}${COLOR_RESET}"
    elif [ -g "$item_name" ]; then
    colored_name="${COLOR_SGID}${base_name}${COLOR_RESET}"
    elif [ -k "$item_name" ]; then
    colored_name="${COLOR_STICKY}${base_name}${COLOR_RESET}"
    elif [ -w "$item_name" ] && [ -d "$item_name" ]; then
    colored_name="${COLOR_OW}${base_name}${COLOR_RESET}"
    else
    colored_name="${COLOR_DEFAULT}${base_name}${COLOR_RESET}"
    fi
    fi

    if [ -n "$description" ]; then
    echo -e "${colored_name} ${description}"
    else
    echo -e "${colored_name}"
    fi
    }

    # Function to recursively print the directory tree structure
    function print_tree() {
    local directory=$1
    local prefix=$2
    local files=()

    if $show_hidden; then
    files=("$directory"/{.,}*)
    else
    files=("$directory"/*)
    fi

    # Filter out non-existent files and directories (handle empty directories)
    files=(${files[@]/*\*/})

    local last_index=$((${#files[@]} - 1))

    for i in "${!files[@]}"; do
    local file="${files[$i]}"
    local basename=$(basename "$file")
    local new_prefix="$prefix"

    if [ "$basename" == "." ] || [ "$basename" == ".." ]; then
    continue
    fi

    if [ $i -eq $last_index ]; then
    echo -n "${prefix}└── "
    new_prefix="$prefix "
    else
    echo -n "${prefix}├── "
    new_prefix="$prefix"
    fi

    if [ -d "$file" ]; then
    dir_count=$((dir_count + 1))
    print_item "$file" -t "folder"
    print_tree "$file" "$new_prefix"
    else
    if $show_size; then
    local size=$(stat -c %s "$file")
    local formatted_size=$(format_size $size)
    print_item "$file" -d "($formatted_size)" -t "file"
    else
    print_item "$file" -t "file"
    fi
    file_count=$((file_count + 1))
    fi
    done
    }

    # Default options
    show_hidden=false
    show_size=false
    dir_count=0
    file_count=0

    # Parse options
    while getopts "as" opt; do
    case $opt in
    a)
    show_hidden=true
    ;;
    s)
    show_size=true
    ;;
    *)
    echo "Usage: $0 [-a] [-s] <directory>"
    echo " -a Show all files including hidden files"
    echo " -s Show file sizes"
    exit 1
    ;;
    esac
    done
    shift $((OPTIND - 1))

    # Check if a directory is provided as an argument
    if [ -z "$1" ]; then
    echo "Usage: $0 [-a] [-s] <directory>"
    echo " -a Show all files including hidden files"
    echo " -s Show file sizes"
    exit 1
    fi

    # Remove trailing slash from the directory if it exists
    directory=$(echo "$1" | sed 's:/*$::')

    # Print the initial directory with color
    print_item "$directory" -t "folder"
    print_tree "$directory" ""

    # Print the summary
    echo -e "\n$dir_count directories, $file_count files"