Skip to content

Instantly share code, notes, and snippets.

@takuzoo3868
Last active July 4, 2024 10:05
Show Gist options
  • Save takuzoo3868/65b0940747175301854000ce7e0b96b7 to your computer and use it in GitHub Desktop.
Save takuzoo3868/65b0940747175301854000ce7e0b96b7 to your computer and use it in GitHub Desktop.
Shell script that reproduced the tree command
#!/usr/bin/env bash
# treeを擬似的に再現したスクリプト
dir_count=0
file_count=0
traverse() {
dir_count=$(expr $dir_count + 1)
local directory=$1
local prefix=$2
local children=($(ls $directory))
local child_count=${#children[@]}
for idx in "${!children[@]}"; do
local child="${children[$idx]}"
local child_prefix="│ "
local pointer="├── "
if [ $idx -eq $(expr ${#children[@]} - 1) ]; then
pointer="└── "
child_prefix=" "
fi
echo "${prefix}${pointer}$child"
[ -d "$directory/$child" ] &&
traverse "$directory/$child" "${prefix}$child_prefix" ||
file_count=$(expr $file_count + 1)
done
}
root="."
[ "$#" -ne 0 ] && root="$1"
echo $root
traverse $root ""
echo
echo "$(expr $dir_count - 1) directories, $file_count files"
@mchccn
Copy link

mchccn commented Mar 17, 2021

since i was too lazy to install tree, thank you very much

@Vouze
Copy link

Vouze commented Jul 7, 2021

You should replace $(expr ...) with $(( ... )).
expr will launch a new process, while $(( )) is a bash internal.

For the same reason, and to handle filenames with spaces, you should replace ($(ls $directory)) with ("$directory"/*). Then you'll have to cut out the directory name.

Here is the result :

#!/usr/bin/env bash

dir_count=0
file_count=0

traverse() {
  dir_count=$((dir_count + 1))
  local directory=$1
  local prefix=$2

  local children=("$directory"/*)
  local child_count=${#children[@]}

  for idx in "${!children[@]}"
  do local child="${children[$idx]}"
     child=${child##*/}
     local child_prefix="│   "
     local pointer="├── "

     if [ $idx -eq $(( ${#children[@]} - 1)) ]; then
       pointer="└── "
       child_prefix="    "
     fi

     echo "${prefix}${pointer}$child"
     [ -d "$directory/$child" ] &&
	 traverse "$directory/$child" "$prefix$child_prefix" ||
	 file_count=$((file_count + 1))
    done
}

root="."
[ "$#" -ne 0 ] && root="$1"
echo $root

traverse $root ""
echo
echo "$((dir_count - 1)) directories, $file_count files"

@toto6038
Copy link

toto6038 commented Mar 9, 2023

The output doesn't handle singular and plural cases as the actual tree command does.

@sbeliakou
Copy link

You should replace (expr...)with(( ... )). expr will launch a new process, while $(( )) is a bash internal.

For the same reason, and to handle filenames with spaces, you should replace ($(ls $directory)) with ("$directory"/*). Then you'll have to cut out the directory name.

Here is the result :

 ...

Thanks a lot, @Vouze! Just updated this script to show trailing backslash for folders, not to print strange tail if the directory is empty, and colored folders and executable files in the tree:

#!/usr/bin/env bash

dir_count=0
file_count=0

traverse() {
  dir_count=$((dir_count + 1))
  local directory=$1
  local prefix=$2

  local children=("$directory"/*)
  local child_count=${#children[@]}

  for idx in "${!children[@]}"
  do local child="${children[$idx]}"
    child=${child##*/}
    local child_prefix="│   "
    local pointer="├── "

    if [ $idx -eq $(( ${#children[@]} - 1)) ]; then
      pointer="└── "
      child_prefix="    "
    fi

    if [ -e $directory/$child ]; then
      if [ -d "$directory/$child" ]; then
        echo -e "${prefix}${pointer}\033[1;34m$child/\033[0m"
        traverse "$directory/$child" "$prefix$child_prefix"
      else
        if [ -x $directory/$child ]; then
          echo -e "${prefix}${pointer}\033[0;32m$child\033[0m"
        else
          echo "${prefix}${pointer}$child"
        fi
        file_count=$((file_count + 1))
      fi
    fi

  done
}

root="."
[ "$#" -ne 0 ] && root="$1"
echo $root

traverse $root ""
echo
echo "$((dir_count - 1)) directories, $file_count files"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment