Como posso cdar para o diretório irmão anterior / seguinte?

10

Costumo ter um layout de diretório de projeto como este

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

Normalmente, eu trabalharei em um dos componentdiretórios, porque é onde estão os arquivos. Quando volto ao shell, muitas vezes simplesmente preciso mudar para um diretório irmão, especialmente quando preciso fazer algumas alterações não-programáveis ​​em todos os componentes. Nesses casos, eu nem vou me importar com o diretório irmão anterior, no qual trabalharei, ou no próximo diretório irmão.

Posso definir um comando prevou nextisso simplesmente cdme levará ao diretório anterior ou ao próximo diretório (por alfabeto ou qualquer outra coisa)? Porque digitar cd ../com<TAB><Arrow keys>o tempo todo está ficando um pouco velho.

Esteis
fonte

Respostas:

8

Não use a solução commandlinefu da outra resposta : é inseguro¹ E ineficiente.² Em vez disso, se você estiver usando bash, use as seguintes funções. Para torná-los persistentes, coloque-os no seu .bashrc. Observe que eu uso a ordem global porque é embutida e fácil. Normalmente, a ordem global é alfabética na maioria dos locais. Você receberá uma mensagem de erro se não houver diretório próximo ou anterior para acessar. Em particular, você verá o erro se tentar nextou prevenquanto estiver no diretório raiz /,.

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

Doesn't Ele não trata de todos os nomes de diretório possíveis. A análise da lssaída nunca é segura .

² cdprovavelmente não precisa ser muito eficiente, mas 6 processos são um pouco excessivos.

jw013
fonte
1
Eu uso o zsh, na verdade, mas se você alterar o primeiro teste no loop for da próxima função para [[ -n $foundcwd ]]sua resposta funcionará no bash e no zsh igualmente bem. Muito bom, e obrigado por escrever isso.
Esteis
4

A função a seguir permite mudar para diretórios irmãos (função 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
}

Um exemplo de uso:

$ 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
fonte
Muito agradável. Estou mantendo a resposta de jw013 como a aceita, porque minha pergunta e caso de uso foram 'Quero passar para o próximo irmão e não me importo com o que é chamado'; mas isso também é útil e bacana, portanto, tenha um voto positivo.
Esteis
2

Encontrei uma dica em commandlinefu.com . Estou reposicionando-o aqui para torná-lo mais localizável, com uma explicação e um nextcomando adicionados enquanto estou nisso.

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

A mágica está no bloco `$ (...). Conecta alguns comandos um ao outro da seguinte maneira:

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
fonte
2
O comando que você encontrou possui vários problemas. Editei-o para corrigir os mais notórios: tratava o nome do diretório como um padrão grep e permitia que ele correspondesse a uma substring; e tinha o nome do diretório sofrer expansão shell (divisão palavra e englobamento) duas vezes (sempre usar aspas em torno de variáveis e comando substituições: "$foo", "$(foo)"). Além disso, a análise da saída de lsnão é confiável , pode falhar com nomes de arquivos contendo caracteres não imprimíveis.
Gilles 'SO- stop be evil'