Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I often have a project directory layout like this

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

I'll usually be working in one of the component directories, because that's where the files are. When I then return to the shell, I'll often simply need to move to a sibling directory, especially when I need to make a few non-scriptable changes to every component. In those cases, I won't even care what the previous sibling directory is that I'll work on, or the next sibling directory.

Can I define a command prev or next that will simply cd me into the previous directory, or next directory (by alphabet or whatever)? Because typing cd ../com<TAB><Arrow keys> all the time is getting a bit old.

share|improve this question
add comment

3 Answers

up vote 6 down vote accepted

Don't use the commandlinefu solution from the other answer: it's unsafe¹ AND inefficient.² Instead, if you are using bash, just use the following functions. To make them persistent, put them into your .bashrc. Note that I use glob order because it's built-in and easy. Typically glob order is alphabetical in most locales though. You'll get an error message if there is no next or previous directory to go to. In particular, you'll see the error if you try to next or prev while in the root directory, /.

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

¹ It doesn't handle all possible directory names. Parsing ls output is never safe.

² cd probably doesn't need to be terribly efficient, but 6 processes is a bit excessive.

share|improve this answer
1  
I use zsh, actually, but if you change the first test in the next function's for-loop to [[ -n $foundcwd ]] then your answer works under bash and zsh equally well. Very nice, and thank you for writing this up. –  Esteis Jun 21 '12 at 2:51
    
@Esteis Thanks for the edit. –  jw013 Jun 21 '12 at 22:11
add comment

The following function permits to change to sibling directories (bash function)

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
}

An example of use :

$ 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
share|improve this answer
add comment

I found a tip over at commandlinefu.com. I'm reposting it here to make it more findable, with an explanation and a next command added while I'm at it.

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

The magic is in the `$(...) block. It pipes a few commands into each other as follows:

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
share|improve this answer
1  
The command you found has a number of problems. I've edited it to correct the most egregious ones: it was treating the directory name as a grep pattern and allowed it to match a substring; and it had the directory name undergo shell expansion (word splitting and globbing) twice (always use double quotes around variable and command substitutions: "$foo", "$(foo)"). In addition, parsing the output of ls is unreliable, it may fail with file names containing unprintable characters. –  Gilles Jun 20 '12 at 23:54
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.