Jak mogę przejść do poprzedniego / następnego katalogu rodzeństwa?

10

Często mam taki układ katalogu projektu

project
`-- component-a
|   `-- files...
`-- component-b
|   `-- files...
`-- component-c
    `-- files...

Zazwyczaj będę pracował w jednym z componentkatalogów, ponieważ tam są pliki. Kiedy wracam do powłoki, często po prostu muszę przejść do katalogu rodzeństwa, szczególnie gdy muszę wprowadzić kilka zmian w skrypcie do każdego komponentu. W takich przypadkach nie dbam nawet o to, w którym katalogu z poprzednim rodzeństwem będę pracował, ani w następnym katalogu rodzeństwa.

Czy mogę zdefiniować polecenie prevlub nextpo prostu cdprzejdę do poprzedniego katalogu lub następnego katalogu (alfabetycznie lub cokolwiek innego)? Ponieważ pisanie przez cd ../com<TAB><Arrow keys>cały czas robi się trochę stare.

Esteis
źródło

Odpowiedzi:

8

Nie używaj rozwiązania wiersza polecenia z innej odpowiedzi : jest to niebezpieczne¹ ORAZ nieefektywne. ² Zamiast tego, jeśli używasz bash, skorzystaj z następujących funkcji. Aby były trwałe, włóż je do swojego .bashrc. Pamiętaj, że używam globalnego porządku, ponieważ jest wbudowany i łatwy. Zazwyczaj jednak porządek globalny jest alfabetyczny w większości lokalizacji. Otrzymasz komunikat o błędzie, jeśli nie ma następnego lub poprzedniego katalogu, do którego można przejść. W szczególności, zobaczysz błąd, jeśli spróbujesz nextlub prevgdy w katalogu głównym /.

## bash and zsh only!
# functions to cd to the next or previous sibling directory, in glob order

prev () {
    # default to current directory if no previous
    local prevdir="./"
    local cwd=${PWD##*/}
    if [[ -z $cwd ]]; then
        # $PWD must be /
        echo 'No previous directory.' >&2
        return 1
    fi
    for x in ../*/; do
        if [[ ${x#../} == ${cwd}/ ]]; then
            # found cwd
            if [[ $prevdir == ./ ]]; then
                echo 'No previous directory.' >&2
                return 1
            fi
            cd "$prevdir"
            return
        fi
        if [[ -d $x ]]; then
            prevdir=$x
        fi
    done
    # Should never get here.
    echo 'Directory not changed.' >&2
    return 1
}

next () {
    local foundcwd=
    local cwd=${PWD##*/}
    if [[ -z $cwd ]]; then
        # $PWD must be /
        echo 'No next directory.' >&2
        return 1
    fi
    for x in ../*/; do
        if [[ -n $foundcwd ]]; then
            if [[ -d $x ]]; then
                cd "$x"
                return
            fi
        elif [[ ${x#../} == ${cwd}/ ]]; then
            foundcwd=1
        fi
    done
    echo 'No next directory.' >&2
    return 1
}

¹ Nie obsługuje wszystkich możliwych nazw katalogów. Przetwarzanie lsdanych wyjściowych nigdy nie jest bezpieczne .

² cdprawdopodobnie nie musi być strasznie wydajne, ale 6 procesów jest nieco nadmiernych.

jw013
źródło
1
Właściwie używam zsh, ale jeśli zmienisz pierwszy test w pętli for następnej funkcji, [[ -n $foundcwd ]]wtedy twoja odpowiedź działa równie dobrze pod bash i zsh. Bardzo miło i dziękuję za napisanie tego.
Esteis
4

Poniższa funkcja pozwala na przejście do katalogów rodzeństwa (funkcja bash)

function sib() {
    ## sib  search sibling directories 
    ##   prompt for choice (when two or more directories are found, current dir is removed from choices) 
    ##   change to directory after selection 
    local substr=$1
    local curdir=$(pwd)
    local choices=$(find .. -maxdepth 1 -type d -name "*${substr}*" | grep -vE '^..$' | sed -e 's:../::' | grep -vE "^${curdir##*/}$" | sort)
    if [ -z "$choices" ]; then
        echo "Sibling directory not found!"
        return
    fi
    local count=$(echo "$choices" | wc -l)
    if [[ $count -eq 1 ]]; then
        cd ../$choices
        return 
    fi
    select dir in $choices; do
        if [ -n "$dir" ]; then
            cd ../$dir
        fi
        break
    done
}

Przykład zastosowania:

$ tree
  .
  ├── component-aaa-01
  ├── component-aaa-02
  ├── component-bbb-01
  ├── component-bbb-02
  ├── component-ccc-01
  ├── component-ccc-02
  └── component-ccc-03
  7 directories, 0 files
  $ cd component-aaa-01/
  $ sib bbb-01
  $ pwd
  component-bbb-01
  $ sib bbb
  $ pwd
  component-bbb-02
  $ sib ccc
  1) component-ccc-01
  2) component-ccc-02
  3) component-ccc-03
  #? 3
  $ pwd
  component-ccc-03
  $ sib 01
  1) component-aaa-01
  2) component-bbb-01
  3) component-ccc-01
  #? 2
  $ pwd
  component-bbb-01
kitekat75
źródło
Bardzo dobrze. Zachowuję odpowiedź jw013 jako zaakceptowaną, ponieważ moje pytanie i przypadek użycia brzmiały: „Chcę przejść do następnego rodzeństwa i nie obchodzi mnie, jak się nazywa”; ale to też jest przydatne i sprytne, więc poproś o opinię.
Esteis
2

Znalazłem przewrót na Commandlinefu.com . Ponownie umieszczam go tutaj, aby był łatwiejszy do znalezienia, wraz z wyjaśnieniem i nextpoleceniem dodanym, gdy jestem przy nim.

alias prev='cd ../"$(ls -F .. | grep '/' | grep -B1 -xF "${PWD##*/}/" | head -n 1)"'
alias next='cd ../"$(ls -F .. | grep '/' | grep -A1 -xF "${PWD##*/}/" | tail -n 1)"'

Magia jest w bloku `$ (...). Potokuje kilka poleceń w następujący sposób:

ls -F .. |   # list items in parent dir; `-F` requests filetype indicators
grep '/' |   # select the directories (written as  `mydir/`)
grep -B1 -xF "${PWD##*/}/" |   # search for the name of the current directory in the output;
                               # print it and the line preceding it
head -n 1    # the first of those two lines contains the name of the previous sibling
Esteis
źródło
2
Znalezione polecenie ma wiele problemów. Zredagowałem go, aby poprawić najbardziej skandaliczne: traktował nazwę katalogu jako wzór grep i pozwalał dopasować podłańcuch; i miał nazwę katalogu przechodzą powłoki ekspansji (podział na słowa i uogólniające) dwa razy (zawsze używać cudzysłowie zmiennych i komend zmiana: "$foo", "$(foo)"). Ponadto parsowanie danych wyjściowych lsjest niewiarygodne , może się nie powieść w przypadku nazw plików zawierających znaki niedrukowalne.
Gilles „SO- przestań być zły”