tags:

views:

739

answers:

15

I have a problem to manage long paths. How can I get quickly to paths like

/Users/User/.../.../.../.../.../Dev/C/card.c

I tried an alias

alias cd C='cd /Users/User/.../.../.../.../.../Dev/C'

but I am unable to do aliases for two separate words. I have long lists of Bash aliases and paths in CDPATH, so I am hesitating to make them more. How can manage long paths?

[Ideas for Replies]

The user litb's reply revealed some of my problems in the management. Things, such as "CTRL+R", "!-3:1:2:4:x" and "incremental search", are hard for me. They probably help in navigating long directories and, in the sense, management.

+4  A: 

Create softlinks in your home directory (or somewhere else of your choosing)

ln -s longDirectoryPath ~/MySoftLinkName

See man ln for more details.

Dan Breslau
Sorry, it took me three days to understand your reply. For future readers, the signs > and < are not about redirecting stdout or anything like that. If I correctly understood, Dan meant:$ ln -s long_directory_path ~/MySoftLinkName
Masi
Sorry about that brain fault. I'll edit the text to use italics instead of <...>
Dan Breslau
+18  A: 

Consider using symbolic links. I have a ~/work/ directory where I place symlinks to all my current projects.

You may also use shell variables:

c='/Users/User/.../.../.../.../.../Dev/C'

Then:

cd "$c"
Juliano
Great thanks for the tip!
Masi
I faced a problem when I changed directories. I solved it with shell variables and symlinks:Dev='/Users/User/Dev/'site='$Dev/.../C/.../site'They are very powerful combination :)
Masi
Don't forget that you should "quote" your parameter expansions. cd "$c"It also makes it a bit more cumbersome again; but you **should** stick to this practice. So yeah, symlinks are probably the best way.
lhunath
@lhunath: good catch! thanks!
Juliano
@lhunanth Sorry I cannot see no difference between:cd $candcd "$c"Is it about protecting kernel from me or something?
Masi
@UnixBasics: If the directory contain spaces, $c will become two or more arguments, that is probably not what you want. If $c doesn't contain spaces, it is fine to just cd $c.
Juliano
@UnixBasics: Moreover, quotes also protect against pathname expansion. The following will also break for a different reason entirely: foo="/Pictures/ * Mom"; cd $foo (If you put bullet points in your picture tag dirnames: * will expand to all directories in your **current** path!)
lhunath
A: 

Look into pushd, which allows you to maintain a stack of directories which you can push onto, pop off of, or rearrange.

jhs
Very good tip, I am regularly using them. I added some of my Bash-commands as an answer. It looks better in the way.
Masi
You're welcome. There is no silver bullet for what you are doing, but if you put together several of the very good suggestions you see here, you'll be quite productive!
jhs
+4  A: 

Probably the easiest solution is to use:

alias cdc='cd /Users/User/.../.../.../.../.../Dev/C'
alias cdbin='cd /Users/User/.../.../.../.../.../Dev/bin'
alias cdtst='cd /Users/User/.../.../.../.../.../Dev/tst'

if you're only really working on one project at a time. If you work on multiple projects, you could have another alias which changed the directories within those aliases above.

So, you'd use something like:

proj game17
cdc
make
proj roman_numerals
cdbin
rm -f *
proj game17 ; cdc

Since this is a useful thing to have, I decided to put together a series of scripts that can be used. They're all based aroung a configuration file that you place in your home directory, along with aliases to source scripts. The file "~/.cdx_data" is of the form:

scrabble:top=~/dev/scrabble
scrabble:src=~/dev/scrabble/src
scrabble:bin=~/dev/scrabble/bin

sudoku:top=~/dev/scrabble
sudoku:src=~/dev/scrabble/src
sudoku:bin=~/dev/scrabble/bin
sudoku:data=~/dev/scrabble/data

and lists all the relevant projects (scrabble and sodoku in this case) and their directories (which may be different for each project, but have top, bin, src and data in this example).

The first action is to initialize stuff, so put:

. ~/.cdx_init

at the end of your .bash_profile and create the "~/.cdx_init" file as:

alias cdxl='. ~/.cdx_list'
alias projl='. ~/.cdx_projlist'
alias cdx='. ~/.cdx_goto'
alias proj='. ~/.cdx_proj'

This sets up the four aliases to source the files which I'll include below. Usage is:

cdxl     - List all directories in current project.

projl    - List all projects.

proj     - Show current project.
proj <p> - Set current project to <p> (if allowed).

cdx      - Show current project/directory and expected/actual real
           directory, since they can get out of sync if you mix cd and cdx.
cdx .    - Set actual real directory to expected directory (in other words,
           get them back into sync).
cdx <d>  - Set directory to <d> (if allowed).

The actual script follow. First, ".cdx_list" which just lists the allowed directories in the current project (pipelines are broken into multiple lines for readability but they should all be on one line).

echo "Possible directories are:"
cat ~/.cdx_data
    | grep "^${CDX_PROJ}:"
    | sed -e 's/^.*://' -e 's/=.*$//'
    | sort -u
    | sed 's/^/    /'

Similarly, ".cdx_projlist" shows all the possible projects:

echo "Possible projects are:"
cat ~/.cdx_data
    | grep ':'
    | sed 's/:.*$//'
    | sort -u
    | sed 's/^/    /'

In the meaty scripts, ".cdx_proj" sets and/or shows the current project:

if [[ "$1" != "" ]] ; then
    grep "^$1:" ~/.cdx_data >/dev/null 2>&1
    if [[ $? != 0 ]] ; then
        echo "No project name '$1'."
        projl
    else
        export CDX_PROJ="$1"
    fi
fi
echo "Current project is: [${CDX_PROJ}]"

and ".cdx_goto" is the same for directories within the project:

if [[ "$1" == "." ]] ; then
    CDX_TMP="${CDX_DIR}"
else
    CDX_TMP="$1"
fi
if [[ "${CDX_TMP}" != "" ]] ; then
    grep "^${CDX_PROJ}:${CDX_TMP}=" ~/.cdx_data >/dev/null 2>&1
    if [[ $? != 0 ]] ; then
            echo "No directory name '${CDX_TMP}' for project '${CDX_PROJ}'."
            cdxl
    else
            export CDX_DIR="${CDX_TMP}"
            cd $(grep "^${CDX_PROJ}:${CDX_DIR}=" ~/.cdx_data
                | sed 's/^.*=//'
                | head -1
                | sed "s:^~:$HOME:")
    fi
fi
CDX_TMP=$(grep "^${CDX_PROJ}:${CDX_DIR}=" ~/.cdx_data
    | sed 's/^.*=//'
    | head -1
    | sed "s:^~:$HOME:")
echo "Current project   is: [${CDX_PROJ}]"
echo "Current directory is: [${CDX_DIR}]"
echo "                      [${CDX_TMP}]"
echo "Actual  directory is: [${PWD}]"
unset CDX_TMP

It uses three environment variables which are reserved for its own use: "CDX_PROJ", "CDX_DIR" and "CDX_TMP". Other than those and the afore-mentioned files and aliases, there are no other resources used. It's the simplest, yet most adaptable solution I could come up with. Best of luck.

paxdiablo
+2  A: 

Once i cd'ed into such a long directory, i have that in the history. Then i just type Ctrl-R for the "(reverse-i-search)" prompt and type in a few characters, like Dev/C that appear somewhere in the path, and it shows me the command what i issued back then and i can easily jump to it again.

That works pretty well in practice. Because it won't find an entry if you haven't typed that path for quite some time, which would mean doing work to make things easier probably wouldn't be worth the time. But it definitely will find it if you used it recently. Which is exactly what i need.

In some way, it's a self-organizing cache for long commands & path-names :)

Johannes Schaub - litb
Which is unfortunately only useful for absolute paths or for relative cd commands which you happen to have issued from the same location as where you happen to be located at the time you recall the cd command.
lhunath
I'm in the habit of always typing "cd" before i go anywhere totally different and often i start with "cd ~/..." (i don't like "../../" style paths). So the path is almost always relative to ~ or correct anyway. It all depends on your habit. The history will adapt to that which is the nice thing
Johannes Schaub - litb
+2  A: 

You might want to consider using a script like this in your .bashrc. I've used it on a daily basis ever since I read that post. Pretty bloody useful.

wzzrd
Unfortunately, I get some errors. I posted a comment to the url.
Masi
Ah, yes, you might want to replace the 'print' statements with 'echo'. ;-)
wzzrd
Thank you! 1+ for the awesome tip :)
Masi
+17  A: 

Using symlinks is probably the best idea; but you can do it even easier than dumping them all into your home directory.

As you mentioned, BASH has a feature called CDPATH which comes in really handy here.

Just make a hidden folder in your homedir (so it doesn't clutter your homedir too much):

$ mkdir ~/.paths
$ cd ~/.paths
$ ln -s /my/very/long/path/name/to/my/project project
$ ln -s /some/other/very/long/path/to/my/backups backups
$ echo 'CDPATH=~/.paths' >> ~/.bashrc
$ source ~/.bashrc

This creates a directory in your homedir called ".paths" which contains symlinks to all your long directory locations which you regularly use, then sets the CDPATH bash variable to that directory (in your .bashrc) and re-reads the .bashrc file.

Now, you can go to any of those paths from anywhere:

$ cd project
$ cd backups

Leaving you with a short CDPATH, no cluttering aliasses, and more importantly: A really easy way to navigate to those long paths from other applications, such as UI applications, by just going into ~/.paths or adding that directory into your UI application's sidebar or so.

Probably the easiest all-round solution you can have.

lhunath
+1 Very good idea to use hidden files!
Masi
Wow! My mind is blown!
sixtyfootersdude
+2  A: 

The user jhs suggested Pushd and Popd-commands. I share here some of my Bash-scripts that I found in Unix Power Tools -book. They are very cool when your directories get a way too long :)

#Moving fast between directories 
alias pd=pushd 
alias pd2='pushd +2'  
alias pd3='pushd +3' 
alias pd4='pushd +4'

The command 'pushd +n' "rotates" the stack. The reverse command 'popd +n' deletes the n entry of the stack. If your stack gets too long, use 'repeat n popd'. For examle, your stack is 12 directories long:

repeat 11 popd

When you want to see your stack, write 'pushd'. For further reading, I recommend the book on pages 625-626.

Masi
A: 

Check out autojmp or dirmarks

Andrew Medico
A: 

Management requires both fast creation and removal of directories. Create many directiories:

mkdir -p user/new_dir/new/_dir/.../new_dir

Remove recursively many directories (be very careful when you are in lower directories!):

rm -r dir/.../new_dir/

For further reading, the cheat sheet may help you:

http://www.scribd.com/doc/2082838/Bash-Command-Line-History-Cheat-Sheet

It contains some nuggets, but I find it rather hard to read. I cannot get commands, like Meta+>, working. They probably help you in navigating long directories.

Masi
+1  A: 

There are fundamental well-known ideas, like creating aliases:

alias cdfoo="cd /long/path/to/foo"

and also "dropping pebbles"

export foo=/long/path/to/foo

and also making the above "project-based". I use 'ticket based' directories.

 topdir=ticket_12345
 alias cdfoo="cd home/me/sandbox/$topdir/long/path/to/foo"
 export foo="/home/me/sandbox/$topdir/long/path/to/foo"

but beyond all this, sometimes it's just handy to jump back and forth to where you've been recently, using command-line menus. (pushd and popd are cumbersome, IMHO).

I use acd_func.sh (listed below). Once defined, you can do

cd --

to see a list of recent directories, with a numerical menu

cd -2

to go to the second-most recent directory.

Very easy to use, very handy.

Here's the code:

   # Insert into .profile, .bash_profile or wherever
    # acd_func 1.0.5, 10-nov-2004
    # petar marinov, http:/geocities.com/h2428, this is public domain

    cd_func ()

{
  local x2 the_new_dir adir index
  local -i cnt

  if [[ $1 ==  "--" ]]; then
    dirs -v
    return 0
  fi

  the_new_dir=$1
  [[ -z $1 ]] && the_new_dir=$HOME

  if [[ ${the_new_dir:0:1} == '-' ]]; then
    #
    # Extract dir N from dirs
    index=${the_new_dir:1}
    [[ -z $index ]] && index=1
    adir=$(dirs +$index)
    [[ -z $adir ]] && return 1
    the_new_dir=$adir
  fi

  #
  # '~' has to be substituted by ${HOME}
  [[ ${the_new_dir:0:1} == '~' ]] && the_new_dir="${HOME}${the_new_dir:1}"

  #
  # Now change to the new dir and add to the top of the stack
  pushd "${the_new_dir}" > /dev/null
  [[ $? -ne 0 ]] && return 1
  the_new_dir=$(pwd)

  #
  # Trim down everything beyond 11th entry
  popd -n +11 2>/dev/null 1>/dev/null

  #
  # Remove any other occurence of this dir, skipping the top of the stack
  for ((cnt=1; cnt <= 10; cnt++)); do
    x2=$(dirs +${cnt} 2>/dev/null)
    [[ $? -ne 0 ]] && return 0
    [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
    if [[ "${x2}" == "${the_new_dir}" ]]; then
      popd -n +$cnt 2>/dev/null 1>/dev/null
      cnt=cnt-1
    fi
  done

  return 0
}

alias cd=cd_func

if [[ $BASH_VERSION > "2.05a" ]]; then
  # ctrl+w shows the menu
  bind -x "\"\C-w\":cd_func -- ;"
fi
Leonard
+1  A: 

This might also be a useful function to put in your .bashrc; it moves up either a number of directories, or to a named directory, i.e. if you're in /a/b/c/d/ you can do up 3 or up a to end up in a.

I have no idea where I found this; if you know, please comment or add the attribution.

function up()
{
    dir=""
    if [ -z "$1" ]; then
        dir=..
    elif [[ $1 =~ ^[0-9]+$ ]]; then
        x=0
        while [ $x -lt ${1:-1} ]; do
            dir=${dir}../
            x=$(($x+1))
        done
    else
        dir=${PWD%/$1/*}/$1
    fi
    cd "$dir";
}
Erik Hesselink
Are you sure that it is right? For some reason, commands, such as "up 3", do not do anything to me. There is even no error message.
Masi
+1 Awesome script, works great for me.
Ian
+1  A: 

If you want to switch to zsh, this is very easy-- just use "alias -g" (global alias, i.e. an alias that works anywhere in the command, not just the first word).

# alias -g c=/my/super/long/dir/name
# cd c
# pwd
/my/super/long/dir/name

In bash, I think the closest thing you'll get to 'aliasing' style is to write a function:

function ccd {
  case "$1" in
    c) cd /blah/blah/blah/long/path/number/one ;;
    foo) cd /blah/blah/totally/different path ;;
    "multiword phrase") cd /tmp ;;
  esac
}

This means using something other than "cd" as the command when you want a shortcut, but other than that, it's flexible; you can also add an "ls" to the function so that it always reminds you what's in the directory after you cd, etc.

(Note that to use a multiword argument as above, you need to quote it on the command line, like this:

ccd "multiword phrase"

so it's not really all that convenient. But it'll work if you need to.)

fholo
+1  A: 

Based on Andrew Medico's suggestion, check out J

Dario
+3  A: 

Revisiting. Today I received this link from a social bookmarking site, then I immediately remembered this question:

Navigation with bm

We keep a simple, plain text bookmarks file and use a tool called bm to do the look-ups. The tool can also be used to edit the bookmark index dynamically as shown below where we add the directories from the previous example to the index.

Juliano
This is a great small command. Thank you for pointing it out!
Masi