Is there a terminal command that navigates to the deepest sub-directory in a directory?
With zsh
:
bydepth() REPLY=${REPLY//[^\/]}
cd Dropbox/**/*(D/O+bydepth[1])
We define a bydepth
sorting function that returns the file with the characters other than /
removed (so the order after that transformation is on depth) and use recursive globbing (**/
being any level of subdirectories) with glob qualifiers:
D
to also consider hidden dirs/
for only dirsO+bydepth
: reverse sort by depth[1]
get the first one only (after sorting).
With bash
and GNU tools, the equivalent would be something like:
IFS= read -rd '' deepest < <(find Dropbox/ -type d -print0 |
awk -v RS='\0' -v ORS='\0' -F / '
NF > max {max = NF; deepest = $0}
END {if (max) print deepest}') && cd -- "$deepest"
(in case of ties, the chosen one will not necessarily be the same as in the zsh
approach).
With your new extra requirement, it becomes more complicated. Basically, if I understand correctly, in case of ties, it should change to the directory that is the deepest common parent of all those directories at the maximum depth. With zsh
:
cd_deepest() {
setopt localoptions rematchpcre
local REPLY dirs result dir match
dirs=(${1:-.}/**/*(ND/nOne:'
REPLY=${#REPLY//[^\/]}-$REPLY':))
(($#dirs)) || return
result=$dirs[1]
for dir ($dirs[2,-1]) {
[[ $result//$dir =~ '^([^-]*-.*)/.*//\1/' ]] || break
result=$match[1]
}
cd -- ${result#*-} && print -rD -- $PWD
}
Example:
$ tree Dropbox
Dropbox
├── a
│ └── b
│ ├── 1
│ │ └── x
│ └── 2
│ └── x
└── c
└── d
└── e
9 directories, 0 files
$ cd_deepest Dropbox
~/Dropbox/a/b
(Dropbox/a/b/1/x
and Dropbox/a/b/2/x
are the deepest ones, and we change to their deepest common parent (Dropbox/a/b
)).
find . -type d -print0 | while IFS= read -r -d $'\0' line; do echo -n $(echo "$line" | grep -o '/' | wc -l); echo " $line"; done | sort | tail -1 | cut -d' ' -f2-
Tested on macOS (bash) and Arch Linux (zsh and bash).
find . -type d
is used to find all directories in the current path.-print0
in combination withread
is used to handle the output from find also for directories that may contain spaces.grep -o
is used to pick out the slashes from the paths.wc -l
is used to count the number of slashes.sort
andtail
are used to pick out the path that contains the most slashes.cut
is used to discard the slash number and only show the path to the deepest directory.
Here's a bash-centric version; it relies on the following bash features:
- globstar shell option to enable directory and subdirectory expansion with
**/
- read that supports arrays to count directory depths
cdd() {
local _cdd_unset_globstar=0
shopt -q globstar || _cdd_unset_globstar=1
shopt -s globstar
local _cdd_deepest=$1
local _cdd_level=1
local _cdd_array=()
for d in "${1}/"**/
do
IFS=/ read -r -d '' -a _cdd_array <<< "$d" || true
if [ "${#_cdd_array[*]}" -gt "$_cdd_level" ]
then
_cdd_deepest=$d
_cdd_level=${#_cdd_array[*]}
fi
done
cd -- "$_cdd_deepest" && true
local _cdd_ret="$?"
[ "$_cdd_unset_globstar" -eq 1 ] && shopt -u globstar
return "$_cdd_ret"
}
The function does the following things:
- checks to see if the globstar shell option is already set; if not, then we save a flag to reset the option at the end.
- initialize the currently-deepest known directory and it's level (
$1
and1
, respectively). - Expand every subdirectory under the given parameter and loop over them.
- For each subdirectory, read it into an array, delimited by
/
; count the number of elements in the array and compare it to the currently-known deepest directory level. If it's deeper, reset those variables. - Once we have the deepest subdirectory,
cd
to it. - If we should reset the globstar shell option, do so.
If it seems cleaner to you to use a subshell to set the shell options, then you could approach it with two functions: a wrapper and a subshell-invoking function that does the above:
cdd_helper() (
shopt -s globstar
_cdd_deepest=$1
_cdd_level=1
for d in "${1}/"**/
do
IFS=/ read -r -d '' -a _cdd_array <<< "$d" || true
if [ "${#_cdd_array[*]}" -gt "$_cdd_level" ]
then
_cdd_deepest=$d
_cdd_level=${#_cdd_array[*]}
fi
done
printf "%s" "$_cdd_deepest"
)
cdd() {
cd -- "$(cdd_helper "$1")"
}