Existe um comando de terminal que navega para o subdiretório mais profundo em um diretório?

5

O que quero dizer é que, suponha que tenhamos o seguinte diretório

Dropbox (pasta)

--- > Bob (pasta)

------- > 2017 (pasta)

------------ > imagens (pasta)

---------------- > image.png (arquivo)

eu poderia fazer cd Dropbox , mas preciso navegar manualmente até o diretório mais profundo images .

Existe um comando como cd Dropbox:deepest directory que me levará para Dropbox/Bob/2017/images ?

Se houver um empate em qualquer nível, pare nesse nível

    
por K Split X 09.06.2017 / 21:47

5 respostas

7

com zsh :

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

Definimos uma função de classificação bydepth que retorna o arquivo com os caracteres diferentes de / removido (para que a ordem após essa transformação esteja em profundidade) e use globalização recursiva ( **/ sendo qualquer nível de subdiretórios) com qualificadores glob:

  • D também considera diretivas ocultas
  • / apenas para dirs
  • O+bydepth : inverter ordenar por profundidade
  • [1] obtém apenas o primeiro (após a classificação).

Com as ferramentas bash e GNU, o equivalente seria algo como:

IFS= read -rd '' deepest < <(find Dropbox/ -type d -print0 |
  awk -v RS='
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 =~ '^([^-]*-.*)/.*///' ]] || break
    result=$match[1]
  }
  cd -- ${result#*-} && print -rD -- $PWD
}
' -v ORS='
$ tree Dropbox
Dropbox
├── a
│   └── b
│       ├── 1
│       │   └── x
│       └── 2
│           └── x
└── c
    └── d
        └── e

9 directories, 0 files
$ cd_deepest Dropbox
~/Dropbox/a/b
' -F / ' NF > max {max = NF; deepest = $0} END {if (max) print deepest}') && cd -- "$deepest"

(no caso de empates, o escolhido não será necessariamente o mesmo que na abordagem zsh ).

Com o seu novo requisito extra, torna-se mais complicado. Basicamente, se eu entendi corretamente, no caso de empates, ele deve mudar para o diretório que é o pai comum mais profundo de todos esses diretórios na profundidade máxima. Com zsh :

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

Exemplo:

IFS= read -rd '' deepest < <(find Dropbox/ -type d -print0 |
  awk -v RS='
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 =~ '^([^-]*-.*)/.*///' ]] || break
    result=$match[1]
  }
  cd -- ${result#*-} && print -rD -- $PWD
}
' -v ORS='
$ tree Dropbox
Dropbox
├── a
│   └── b
│       ├── 1
│       │   └── x
│       └── 2
│           └── x
└── c
    └── d
        └── e

9 directories, 0 files
$ cd_deepest Dropbox
~/Dropbox/a/b
' -F / ' NF > max {max = NF; deepest = $0} END {if (max) print deepest}') && cd -- "$deepest"

( Dropbox/a/b/1/x e Dropbox/a/b/2/x são os mais profundos e mudamos para o pai mais comum ( Dropbox/a/b )).

    
por 09.06.2017 / 22:07
2

find . -type d -print0 | while IFS= read -r -d $'find . -type d' line; do echo -n $(echo "$line" | grep -o '/' | wc -l); echo " $line"; done | sort | tail -1 | cut -d' ' -f2-

Testado no macOS (bash) e no Arch Linux (zsh e bash).

  • -print0 é usado para encontrar todos os diretórios no caminho atual.
  • read em combinação com grep -o é usado para manipular a saída de find também para diretórios que podem conter espaços.
  • wc -l é usado para selecionar as barras dos caminhos.
  • sort é usado para contar o número de barras.
  • tail e cut são usados para escolher o caminho que contém mais barras.
  • %code% é usado para descartar o número da barra e mostrar apenas o caminho para o diretório mais profundo.
por 09.06.2017 / 22:30
2

Aqui está uma versão centrada no bash; ele se baseia nos seguintes recursos 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"
}

A função faz o seguinte:

  1. verifica se a opção do shell globstar já está configurada; se não, então salvamos um sinalizador para redefinir a opção no final.
  2. inicialize o diretório conhecido atualmente mais profundo e seu nível ( **/ e $1 , respectivamente).
  3. Expanda todos os subdiretórios sob o parâmetro especificado e faça um loop sobre eles.
  4. Para cada subdiretório, leia-o em uma matriz, delimitada por 1 ; conte o número de elementos na matriz e compare-o com o nível de diretório mais profundo atualmente conhecido. Se for mais profundo, redefina essas variáveis.
  5. Quando tivermos o subdiretório mais profundo, / .
  6. Se precisarmos redefinir a opção do shell globstar, faça isso.

Se parecer mais limpo para você usar um subshell para definir as opções do shell, você pode abordá-lo com duas funções: um wrapper e uma função de invocação de subshell que faz o seguinte:

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")"
}
    
por 11.06.2017 / 04:03
1

Uma solução alternativa usando find e sort para determinar os diretórios mais profundos:

$ pwd
/home/gv/Desktop/PythonTests
$ find $PWD -type d -printf '%d:%p
$ cd "$(find $PWD -type d -printf '%d:%p
$ deepest () { cd "$(find $PWD -type d -printf '%d:%p
$ pwd
/home/gv/Desktop/PythonTests
$ find $PWD -type d -printf '%d:%p
$ cd "$(find $PWD -type d -printf '%d:%p
$ deepest () { cd "$(find $PWD -type d -printf '%d:%p%pre%' |sort -z -t: -r |awk -F: -v RS='%pre%' -v ORS='\n' 'NR==1{print $2}')"; }

$ pwd
/home/gv/Desktop/PythonTests
$ deepest
$ pwd
/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5
' |sort -z -t: -r |awk -F: -v RS='%pre%' -v ORS='\n' 'NR==1{print $2}')" $ pwd /home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5
' |sort -z -t: -r |awk -F: -v RS='%pre%' -v ORS='\n' 'NR<=3' #Print the top 3 deepest dirs 5:/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5 #mind the space in last dir name 5:/home/gv/Desktop/PythonTests/.git/logs/refs/remotes/origin 4:/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4
' |sort -z -t: -r |awk -F: -v RS='%pre%' -v ORS='\n' 'NR==1{print $2}')"; } $ pwd /home/gv/Desktop/PythonTests $ deepest $ pwd /home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5
' |sort -z -t: -r |awk -F: -v RS='%pre%' -v ORS='\n' 'NR==1{print $2}')" $ pwd /home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5
' |sort -z -t: -r |awk -F: -v RS='%pre%' -v ORS='\n' 'NR<=3' #Print the top 3 deepest dirs 5:/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5 #mind the space in last dir name 5:/home/gv/Desktop/PythonTests/.git/logs/refs/remotes/origin 4:/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4

Com a opção -printf de find você pode selecionar o que imprimir:
%d : A profundidade do diretório do cwd
%p : O nome do resultado = nome do diretório
%code% : nul delimiter

Você pode entrar no primeiro diretório mais profundo como:

%pre%

Para evitar a lembrança de tudo isso, você pode inserir uma função no arquivo de alias para ser carregada com seu perfil bash:

%pre%     
por 11.06.2017 / 23:57
1

Primeira maneira - recursão, melhor na minha opinião.

Uso: carrega a função rcd no bash source recur_cd.sh e testa: rcd some_dir .

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

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

Segunda via - comando find (duas variantes aqui).

Uso: Primeiramente, carregue duas funções internas ( dcd1 e dcd2 ) no bash , usando este comando: source deep_cd.sh . As funções fazem o mesmo, mas implementadas de forma diferente.

Em seguida, teste-o:

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

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

Terceira via - uso de macros readline.

bind '"\e\C-i":"\C-i\C-i\C-i\C-i"' Isso é tudo:)

Explicação:

  • bind - leia help bind . É a função readline para ligação de chaves.
  • \e\C-i - Ctlr + Alt + i - nova combinação de ligações, quando for pressionada, fará séries de preenchimento automático, antes de duas ou mais diretórios emergem.
  • \C-i é o mesmo que Tab , que significa 'completo'. Você precisa de tantos \C-i quanto supor a profundidade do diretório. Se supostos 10 níveis, você precisa de 10% de blocos\C-i. No entanto, pode ser mais conveniente escolher o número médio de blocos, como 3 ou 4, e pressionar Alt + Ctrl + i combinação duas vezes, onde é necessário. Isso evitará a repetição da saída (consulte os comentários na seção "Teste" )

Se você quiser tornar esse comportamento permanente, adicione essa linha ao .bashrc

Teste

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
    
por 12.06.2017 / 10:07