是否有一个终端命令可以导航到目录中最深的子目录?

是否有一个终端命令可以导航到目录中最深的子目录?

我的意思是,假设我们有以下目录

Dropbox(文件夹)

---> 鲍勃(文件夹)

-------->2017(文件夹)

------------> 图像(文件夹)

----------------> 图像.png (文件)

我可以这样做 cd Dropbox,但随后我必须手动导航到最深的目录images

有这样的命令 cd Dropbox:deepest directory可以带我去吗Dropbox/Bob/2017/images

如果任一级别存在平局,则停止在该级别

答案1

zsh

bydepth() REPLY=${REPLY//[^\/]}
cd Dropbox/**/*(D/O+bydepth[1])

我们定义一个bydepth排序函数,返回包含未删除字符的文件/(因此转换后的顺序是在深度上),并使用**/带有 glob 限定符的递归 globbing(是任何级别的子目录):

  • D还要考虑隐藏目录
  • /仅适用于目录
  • O+bydepth:按深度反向排序
  • [1]仅获取第一个(排序后)。

使用bashGNU 工具,相当于:

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"

(如果出现平局,所选择的方法不一定与zsh方法中的相同)。

有了新的额外要求,事情就会变得更加复杂。基本上,如果我理解正确,在有联系的情况下,它应该更改为最大深度的所有这些目录的最深公共父目录。和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
}

例子:

$ 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/xDropbox/a/b/2/x是最深的,我们将其更改为它们最深的共同父级(Dropbox/a/b))。

答案2

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-

在 macOS (bash) 和 Arch Linux(zsh 和 bash)上进行了测试。

  • find . -type d用于查找当前路径下的所有目录。
  • -print0结合read使用来处理 find 的输出,也用于可能包含空格的目录。
  • grep -o用于从路径中挑选出斜杠。
  • wc -l用于计算斜杠的数量。
  • sorttail用于挑选包含最多斜杠的路径。
  • cut用于丢弃斜线号并仅显示最深目录的路径。

答案3

这是一个以 bash 为中心的版本;它依赖于以下 bash 功能:

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"
}

该函数执行以下操作:

  1. 检查 globstar shell 选项是否已设置;如果没有,那么我们保存一个标志以在最后重置选项。
  2. 初始化当前已知最深的目录及其级别(分别为$11)。
  3. 展开给定参数下的每个子目录并循环遍历它们。
  4. 对于每个子目录,将其读入一个数组,以/;分隔。计算数组中元素的数量并将其与当前已知的最深目录级别进行比较。如果更深,请重置这些变量。
  5. 一旦我们有了最深的子目录,cd就可以了。
  6. 如果我们应该重置 globstar shell 选项,请这样做。

如果您认为使用子 shell 来设置 shell 选项更清晰,那么您可以使用两个函数来处理它:一个包装器和一个执行上述操作的子 shell 调用函数:

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")"
}

答案4

第一种方法 - 递归,我认为最好的方法。

用法:将函数加载rcd到 bash 中source recur_cd.sh,然后测试它:rcd some_dir

rcd () {
    if [ ! -d "$*" ]; then
        return 121 
    else
        rcd "${*}"*/
    fi  

    if (($? == 121)); then 
        cd "$*" 
    fi
}

第二种方式 - 命令 find (这里有两个变体)。

用法:首先,通过以下命令将两个内部函数(dcd1dcd2)加载到 中: 。函数的作用相同,但实现方式不同。bashsource deep_cd.sh

然后,测试一下:

dcd1 dir   # first variant
pwd
cd -       # return back for further testing`<br>
dcd2 dir   # second variant
pwd
           # and so on.

深CD.sh

#!/bin/bash

# first variant, support filenames with spaces, work by awk mainly 
dcd1 () {
    cd "$(find "$1" -type d -printf "%d %p\n" | sort | awk '{
        a[$1]++; 
        tmp_num=$1;
        sub("^[0-9]* ", "", $0);
        path = $0;
        if (a[tmp_num] == 2) { sub("[^/]*$", ""); path = $0; exit; }
    }
    END { print path; }')"
}

# second variant, support filenames with spaces - more programs 
# used (cut, uniq, tail), awk only prints the path, doesn't search it
dcd2 () {
    str=$(find "$1" -type d -printf "%d %p\n" | sort)
    num=$(cut -d " " -f 1 <<< "$str" | uniq -u | tail -n 1)
    path=$(awk -v n=$num 'NR == n+1 { $1=""; print $0; }' <<< "$str")
    cd "${path# }"
}

第三种方法 - readline 宏的使用。

bind '"\e\C-i":"\C-i\C-i\C-i\C-i"'就这样 :)

解释:

  • bind- 读help bind。它是用于键绑定的 readline 函数。
  • \e\C-i- Ctlr+ Alt+ i- 新的键组合,当按下它时,它会在出现两个或更多目录之前自动完成系列。
  • \C-i与 相同Tab,意思是“完整”。您需要的\C-i目录深度与您想象的一样多。如果假设有 10 个级别,那么您需要 10 个\C-i块。不过,选择中间数量的块可能会更方便,例如 3 或 4,然后Alt + Ctrl + i在需要时按组合键两次。它将防止输出重复(请参阅“测试”部分)

如果您想让此行为永久存在,请将此行添加到.bashrc

测试

cd o                        # then Alt + Ctrl + i
cd one/two/three/four/      # directory has changed to deepest

cd A                        # then Alt + Ctrl + i
cd A/B/C/                   # directory has changed to deepest
D/ E/                       # and two inner directories appear
~/deepest_dir$ cd A/B/C/    # one drawback here - if deepest 
D/ E/                       # directory is reached, then completion
~/deepest_dir$ cd A/B/C/    # works idle and print duplicating
                            # output, so may be worth shrinks 'C-i'
                            # blocks amount in the binding string

相关内容