Существует ли команда терминала, которая перемещается в самый глубокий подкаталог в директории?

Вопрос или проблема

Что я имею в виду, это предположим, у нас есть следующий каталог

Dropbox (папка)

—> Bob (папка)

——-> 2017 (папка)

————> images (папка)

—————-> image.png (файл)

Я мог бы сделать
cd Dropbox, но тогда мне придется вручную переходить до самого глубокого каталога images.

Существует ли команда типа
cd Dropbox:deepest directory, которая сразу перенесет меня в Dropbox/Bob/2017/images?

Если на каком-то уровне возникает совпадение, то остановитесь на этом уровне

С zsh:

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

Мы определяем функцию сортировки bydepth, которая возвращает файл с удаленными символами, отличными от / (так, чтобы порядок после этой трансформации был по глубине), и используем рекурсивное глоббинг (**/ обозначает любой уровень подкаталогов) с квалификаторами для глоббинга:

  • D для учета скрытых каталогов
  • / только для каталогов
  • O+bydepth: обратная сортировка по глубине
  • [1] берём только первый (после сортировки).

С bash и инструментами GNU эквивалент будет выглядеть примерно так:

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/x и Dropbox/a/b/2/x являются самыми глубокими, и мы переходим в их общего родителя (Dropbox/a/b)).

find . -type d -print0 | while IFS= read -r -d $'\0' line; do echo -n $(echo "$line" | grep -o "https://unix.stackexchange.com/" | 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 используется для подсчета количества слэшей.
  • sort и tail используются для извлечения пути, содержащего наибольшее количество слэшей.
  • cut используется для исключения количества слэшей и отображения только пути к самому глубокому каталогу.

Вот версия, ориентированная на 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; если нет, то мы записываем флаг, чтобы сбросить опцию в конце.
  2. инициализирует текущий и самый глубокий известный каталог и его уровень ($1 и 1 соответственно).
  3. Расширяет каждый подкаталог под данным параметром и проходит по ним.
  4. Для каждого подкаталога читает его в массив, разделенный по /; подсчитывает количество элементов в массиве и сравнивает его с текущей известной глубиной самого глубокого каталога. Если он глубже, сбрасывает эти переменные.
  5. Когда мы находим самый глубокий подкаталог, выполняем cd на него.
  6. Если мы должны сбросить опцию оболочки globstar, делаем это.

Если вам кажется, что использование подоболочки для установки опций оболочки будет чище, вы можете подойти к этому с помощью двух функций: обёртки и функции подсhell, выполняющей вышеописанное:

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

Альтернативное решение с использованием find и sort для определения самых глубоких каталогов:

$ pwd
/home/gv/Desktop/PythonTests
$ find $PWD -type d -printf '%d:%p\0' |sort -z -t: -r |awk -F: -v RS='\0' -v ORS='\n' 'NR<=3'   #Вывести 3 самых глубоких каталога
5:/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5  #обратите внимание на пробел в последнем имени каталога
5:/home/gv/Desktop/PythonTests/.git/logs/refs/remotes/origin
4:/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4

С опцией -printf у команды find вы можете выбирать, что выводить:
%d: Глубина каталога относительно текущего рабочего каталога
%p : Имя результата = имя каталога
\0 : разделитель nul

Вы можете перейти в первый самый глубокий каталог следующим образом:

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

Чтобы не запоминать всё это, вы можете закрепить функцию в своем файле алиасов, чтобы она загружалась с вашим профилем bash:

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

$ pwd
/home/gv/Desktop/PythonTests
$ deepest
$ pwd
/home/gv/Desktop/PythonTests/tmp/tmp2/tmp3/tmp4/tmp 5

Первый способ – рекурсия, на мой взгляд лучший.

Использование: загрузите функцию rcd в bash source recur_cd.sh, затем протестируйте ее: rcd some_dir.

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

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

Второй способ – команда find (два варианта здесь).

Использование: Сначала загрузите две внутренние функции (dcd1 и dcd2) в bash, с помощью этой команды: source deep_cd.sh. Функции делают одно и то же, но реализованы по-разному.

Затем, протестируйте это:

dcd1 dir   # первый вариант
pwd
cd -       # вернуться назад для дальнейшего тестирования
dcd2 dir   # второй вариант
pwd
           # и так далее.

deep_cd.sh

#!/bin/bash

# первый вариант, поддерживает имена файлов с пробелами, работает в основном через awk 
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; }')"
}

# второй вариант, поддерживает имена файлов с пробелами - используется больше программ 
# (cut, uniq, tail), awk только выводит путь, а не ищет его
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-iCtlr + Alt + i – новая соединенная комбинация, когда она будет нажата, она выполнит серию автодополнений до тех пор, пока не появятся два или несколько каталогов.
  • \C-i то же, что и Tab, означающее ‘дополнить’. Вам нужно столько \C-i, сколько предполагается уровень глубины каталога. Если предполагается 10 уровней, вам нужно 10 блоков \C-i. Хотя, возможно, будет удобнее выбрать среднее количество блоков, скажем, 3 или 4, и просто нажимать комбинацию Alt + Ctrl + i дважды, где это необходимо. Это предотвратит повторение вывода (см. комментарии в разделе “Тестирование”)

Если вы хотите сделать такое поведение постоянным, добавьте эту строку в .bashrc

Тестирование

cd o                        # затем Alt + Ctrl + i
cd one/two/three/four/      # каталог был изменен на самый глубокий

cd A                        # затем Alt + Ctrl + i
cd A/B/C/                   # каталог был изменен на самый глубокий
D/ E/                       # и появились два внутренних каталога
~/deepest_dir$ cd A/B/C/    # одно препятствие здесь - если 
D/ E/                       # достигнут самый глубокий каталог,
~/deepest_dir$ cd A/B/C/    # автодополнение работает вхолостую
                            # и дублирует вывод, так что возможно
                            # стоит уменьшить количество блоков
                            # 'C-i' в строке привязки

Недавно у меня была эта же проблема с приложением, которое создало вложенный ряд из сотен подкаталогов со структурой .trash/<uuid4-32-chars>/.trash/<uuid4-32-chars>/.../.trash/<uuid4-32-chars>.

Мне удалось перейти в самый глубокий каталог с помощью этой команды bash:

find_child_dir="find ./ -mindepth 1 -maxdepth 1 -type d"; while [[ "$(eval "$find_child_dir" | wc -l)" -gt 0 ]]; do cd "$(eval "$find_child_dir" | head -n 1)"; pwd; done;

Объяснение:

  • find ./ -mindepth 1 -maxdepth 1 -type d находит прямых потомков текущего каталога. -mindepth 1 -maxdepth 1 обеспечивает исключение . и .. из результатов. ./ обеспечивает включение скрытых подкаталогов в результаты.
  • find_child_dir="..." сохраняет команду find как строку для последующего использования с eval "$find_child_dir".
  • | wc -l подсчитывает, сколько подкаталогов было найдено.
  • while [[ "$(eval "$find_child_dir" | wc -l)" -gt 0 ]]; продолжает цикл, пока текущий каталог имеет по крайней мере один подкаталог.
  • | head -n 1 получает первый элемент, возвращенный из find
  • cd "eval "$find_child_dir" | head -n 1)" перемещается в первый найденный подкаталог.

Конечно, это предполагает, что у вас есть только один проблемный глубоко вложенный каталог, с которым нужно справиться.

.

Ответ или решение

Вопрос касается автоматизации перехода к самой глубокой поддиректории в заданной директории, что может быть полезно для упрощения навигации по сложным файловым структурам. Давайте разберем несколько подходов к решению этой задачи, используя различные инструменты командной строки, такие как Bash и Zsh.

Решение с использованием Zsh

При использовании оболочки Zsh, вы можете воспользоваться мощной системой глоббирования (расширения имени файла). Следующий скрипт позволяет на основе глубины структуры папок перейти к самой глубокой директории:

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

Этот скрипт выполняет сортировку директорий по глубине и выбирает самую глубокую. Если на каком-то уровне присутствует несколько директорий с одинаковой глубиной, движение будет остановлено на этом уровне. Полезные элементы:

  • `/`** позволяет рекурсивно искать поддиректории.
  • (D) учитывается возможность скрытых директорий.
  • O+bydepth сортирует результаты по глубине.

Bash и GNU утилиты

Для Bash, использование find и awk позволяет выполнить аналогичную операцию:

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"

Этот скрипт находит самый глубокий путь и переходит в него. Он также сортирует потенциальные пути и выбирает наиболее вложенные.

Хранение функции для более удобного использования

Создание собственной Bash функции, такой как cdd, может существенно упростить задачу:

cdd() {
  cd "$(find "$1" -type d -printf '%d:%p\0' | sort -z -t: -r | awk -F: -v RS='\0' 'NR==1{print $2}')"
}

Эта функция использует find, чтобы построить список директорий, отсортированных по глубине, а затем использует cd для перехода. Добавьте эту функцию в ваш .bashrc для повседневного использования.

Альтернативные методы

Вы можете рассмотреть также другие способы, такие как рекурсия и макросы readline, в зависимости от ваших предпочтений и требований. Однако они могут потребовать больше настройки и тестирования.

Каждое из этих решений решает задачу перехода к самой глубокой поддиректории, минимизируя затраты времени на ручную навигацию, а также может быть адаптировано под ваши конкретные нужды. Выбор инструмента зависит от вашего предпочтения и окружения, будь то Bash, Zsh или другие системы.

Оцените материал
Добавить комментарий

Капча загружается...