How to manage Long Paths in Bash? - bash
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.
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.
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"
Create symlinks in your home directory (or somewhere else of your choosing)
ln -s longDirectoryPath ~/MySymLinkName
See man ln for more details.
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.
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.
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 :)
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.
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.
In your .bashrc find PS1='${debian_chroot:+($debian_chroot)}[\033[01;32m]\u#\h[\033[00m]:[\033[01;34m]
\W[\033[00m]\$ '
and replace the \w with \W.I already have it changed here. This will only give you the main directory where you are working. You can get the full directory by typing pwd
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
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";
}
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.)
Based on Andrew Medico's suggestion, check out J
Look into pushd, which allows you to maintain a stack of directories which you can push onto, pop off of, or rearrange.
Check out autojmp or dirmarks
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.
I realize the question is pretty old, but none of the scripts out there satisfied me, so I wrote a new one.
Here's the requirements I had in mind:
1) Use only bash commands -- I intend to use this on many different unices -- Linux, cygwin, HP-UX, AIX, and a couple others, so I couldn't depend on grep being consistent. Luckily I do have bash everywhere I work.
2) Short code -- I wanted to be able to bind this to a key in GNU screen, and just hit that key to paste the script into the current bash shell I'm using, so that I don't have to setup bash profiles on every system I use. Anything super long would be annoying and take too much time to paste.
3) No file usage -- Don't want to be littering shared logons with random files.
4) Act just like "cd" in the normal case. Don't want to have to think about which command to use before I start typing.
5) Provide "up" usage like this answer: How to manage Long Paths in Bash?
6) Keep a list of recently used directories, and switch to the most recent.
Here's the script:
#Jump History - Isaiah Damron
function jfind() {
lp=${JNHIST//==${PWD}==/==}
lp=${lp%%${lp#==*$1*==}}
lp=${lp##${lp%==*$1*==*}}
lp=${lp//==/}
[[ -d "$lp" ]] && echo $lp && return 0
return 1;
}
function jadd() {
[[ -z "$JNHIST" ]] && export JNHIST='=='
[[ 3000 -lt ${#JNHIST} ]] && export JNHIST=${JNHIST:0:3000} && export JNHIST="${JNHIST%==*}=="
export JNHIST="==$PWD${JNHIST//==${PWD}==/==}"
}
function j() {
{ cd $* 2> /dev/null && jadd; } \
|| { cd ${PWD/$1*/}$1 2> /dev/null && jadd; } \
|| { jfind $1 \
&& { cd $( jfind $1 ) 2> /dev/null && jadd; } ; } \
|| cd $*
}
function jh() {
[[ -z "$1" ]] && echo -e ${JNHIST//==/\\n}
[[ -n "$1" ]] && jfind $1 && cd $(jfind $1) && jadd
}
Usage:
jh [parameters]
If called on its own, without any parameters, it outputs the current history list. If it has a parameter, then it searches through the history for the most recently used directory that contains the string $1, and cd's to it.
j {parameters}
Does cd parameters. If that fails, it checks if any of the parent directories of $PWD match $1, and cd's to it. If that fails, then it calls jh $1. If that fails, then it outputs the result of cd parameters
Note: I used '==' as an internal separator. Hopefully you don't have any directories that contain a '==', but if you do you'll have to change around the script. Just :%s/==/whatever/g
Related
Is it possible to CD into a file?
I find a list of files that I need to cd to (obviously to the parent directory). If I do cd ./src/components/10-atoms/fieldset/package.json I get the error cd: not a directory:, which makes sense. But isn't there a way to allow for that? Because manipulating the path-string is pretty cumbersome and to me that would make total sense to have an option for that, since cd is a directory function and it would be cool that if the path would not end up in a file, it would recursively jump higher and find the "first dir" from the given path. So cd ./src/components/10-atoms/fieldset/package.json would put me into ./src/components/10-atoms/fieldset/ without going on my nerves, telling me that I have chosen a file rather than a dir.
You could write a shell function to do it. cd() { local args=() arg for arg in "$#"; do if [[ $arg != -* && -e $arg && ! -d $arg ]]; then args+=("$(dirname "$arg")") else args+=("$arg") fi done builtin cd ${args[0]+"${args[#]}"} } Put it in your ~/.bashrc if you want it to be the default behavior. It won't be inherited by shell scripts or other programs so they won't be affected. It modifies cd's arguments, replacing any file names with the parent directory. Options with a leading dash are left alone. command cd calls the underlying cd builtin so we don't get trapped in a recursive loop. (What is this unholy beast: ${args[0]+"${args[#]}"}? It's like "${args[#]}", which expands the array of arguments, but it avoids triggering a bash bug with empty arrays on the off chance that your bash version is 4.0-4.3 and you have set -u enabled.)
This function should do what you need: cdd() { test -d "$1" && cd "$1" || cd $(dirname "$1") ; } If its first argument "$1" is a directory, just cd into it, otherwise cd into the directory containing it. This function should be improved to take into account special files such as devices or symbolic links.
You can if you enter a bit longer line (or create dedicated shell script) cd $(dirname ./src/components/10-atoms/fieldset/package.json) If you add it in script it can be : cd $(dirname $1) but you need to execute it on this way: . script_name ./src/components/10-atoms/fieldset/package.json
You can put this function in your ~/.bashrc: function ccd() { TP=$1 # destination you're trying to reach while [ ! -d $TP ]; do # if $TP is not a directory: TP=$(dirname $TP) # remove the last part from the path done # you finally got a directory cd $TP # and jump into it } Usage: ccd /etc/postfix/strangedir/anotherdir/file.txt will get you to /etc/postfix.
bash - recursive script can't see files in sub directory
I got a recursive script which iterates a list of names, some of which are files and some are directories. If it's a (non-empty) directory, I should call the script again with all of the files in the directory and check if they are legal. The part of the code making the recursive call: if [[ -d $var ]] ; then if [ "$(ls -A $var)" ]; then ./validate `ls $var` fi fi The part of code checking if the files are legal: if [[ -f $var ]]; then some code fi But, after making the recursive calls, I can no longer check any of the files inside that directory, because they are not in the same directory as the main script, the -f $var if cannot see them. Any suggestion how can I still see them and use them?
Why not use find? Simple and easy solution to the problem.
Always quote variables, you never known when you will find a file or directory name with spaces shopt -s nullglob if [[ -d "$path" ]] ; then contents=( "$path"/* ) if (( ${#contents[#]} > 0 )); then "$0" "${contents[#]}" fi fi you're re-inventing find of course, var is a lousy variable name if you're recursively calling the script, you don't need to hard-code the script name. you should consider putting the logic into a function in the script, and the function can recursively call itself, instead of having to spawn an new process to invoke the shell script each time. If you do this, use $FUNCNAME instead of "$0"
A few people have mentioned how find might solve this problem, I just wanted to show how that might be done: find /yourdirectory -type f -exec ./validate {} +; This will find all regular files in yourdirectory and recursively in all its sub-directories, and return their paths as arguments to ./validate. The {} is expanded to the paths of the files that find locates within yourdirectory. The + at the end means that each call to validate will be on a large number of files, instead of calling it individually on each file (wherein the + is replaced with a \), this provides a huge speedup sometimes.
One option is to change directory (carefully) into the sub-directory: if [[ -d "$var" ]] ; then if [ "$(ls -A $var)" ]; then (cd "$var"; exec ./validate $(ls)) fi fi The outer parentheses start a new shell so the cd command does not affect the main shell. The exec replaces the original shell with (a new copy of) the validate script. Using $(...) instead of back-ticks is sensible. In general, it is sensible to enclose variable names in double quotes when they refer to file names that might contain spaces (but see below). The $(ls) will list the files in the directory. Heaven help you with the ls commands if any file names or directory names contain spaces; you should probably be using * glob expansion instead. Note that a directory containing a single file with a name such as -n would trigger a syntax error in your script. Corrigendum As Jens noted in a comment, the location of the shell script (validate) has to be adjusted as you descend the directory hierarchy. The simplest mechanism is to have the script on your PATH, so you can write exec validate or even exec $0 instead of exec ./validate. Failing that, you need to adjust the value of $0 — assuming your shell leaves $0 as a relative path and doesn't mess around with converting it to an absolute path. So, a revised version of the code fragment might be: # For validate on PATH or absolute name in $0 if [[ -d "$var" ]] ; then if [ "$(ls -A $var)" ]; then (cd "$var"; exec $0 $(ls)) fi fi or: # For validate not on PATH and relative name in $0 if [[ -d "$var" ]] ; then if [ "$(ls -A $var)" ]; then (cd "$var"; exec ../$0 $(ls)) fi fi
pushd popd global directory stack?
I do not know if there is a valid way to do this. But, have always wanted to see if its possible. I know that pushd, popd and dirs are useful for a number of things like copying between directories you have recently visited. But, is there a way in which you can keep a global stack? So that if I push something (using pushd) in one terminal it gets reflected in another (maybe only the terminals in that login session).
You should be able to do this with a pair of shell functions and a temporary file. Your temporary file would be named something like '/home/me/.directory_stack' and would simply contain a list of directories: /home/me /etc /var/log Your 'push_directory' function would simply add the current directory to the list. The 'pop_directory' function would pull the most recent off of the list and switch to that directory. Storing the stack in a file like this ensures that the information exists across all open terminals (and even across reboots). Here are some example functions (warning: only lightly tested) directory_stack=/home/me/.directory_stack function push_dir() { echo $(pwd) >> $directory_stack cd $1 } function pop_dir() { [ ! -s $directory_stack ] && return newdir=$(sed -n '$p' $directory_stack) sed -i -e '$d' $directory_stack cd $newdir } Add that to your .bashrc and they'll automatically be defined every time you log into the shell.
You'll probably want to write a few shell functions and use them in place of pushd and popd. Something like the following (untested) functions might do the job: mypushd() { echo "$1" >> ~/.dir_stack ; cd "$1" } mypopd() { dir=`tail -1 ~/.dir_stack` ; cd "$dir" ; foo=`wc -l ~/.dir_stack | egrep -o '[0-9]+'` ; ((foo=$foo-1)) ; mv ~/.dir_stack ~/.dir_stack_old ; head -n $foo ~/.dir_stack_old > ~/.dir_stack } You could get rid of some of the uglier bits if you write a small program that returns and removes the last line of the file.
Reliable way for a Bash script to get the full path to itself [duplicate]
This question already has answers here: How do I get the directory where a Bash script is located from within the script itself? (74 answers) Closed 6 years ago. I have a Bash script that needs to know its full path. I'm trying to find a broadly-compatible way of doing that without ending up with relative or funky-looking paths. I only need to support Bash, not sh, csh, etc. What I've found so far: The accepted answer to Getting the source directory of a Bash script from within addresses getting the path of the script via dirname $0, which is fine, but that may return a relative path (like .), which is a problem if you want to change directories in the script and have the path still point to the script's directory. Still, dirname will be part of the puzzle. The accepted answer to Bash script absolute path with OS X (OS X specific, but the answer works regardless) gives a function that will test to see if $0 looks relative and if so will pre-pend $PWD to it. But the result can still have relative bits in it (although overall it's absolute) — for instance, if the script is t in the directory /usr/bin and you're in /usr and you type bin/../bin/t to run it (yes, that's convoluted), you end up with /usr/bin/../bin as the script's directory path. Which works, but... The readlink solution on this page, which looks like this: # Absolute path to this script. /home/user/bin/foo.sh SCRIPT=$(readlink -f $0) # Absolute path this script is in. /home/user/bin SCRIPTPATH=`dirname $SCRIPT` But readlink isn't POSIX and apparently the solution relies on GNU's readlink where BSD's won't work for some reason (I don't have access to a BSD-like system to check). So, various ways of doing it, but they all have their caveats. What would be a better way? Where "better" means: Gives me the absolute path. Takes out funky bits even when invoked in a convoluted way (see comment on #2 above). (E.g., at least moderately canonicalizes the path.) Relies only on Bash-isms or things that are almost certain to be on most popular flavors of *nix systems (GNU/Linux, BSD and BSD-like systems like OS X, etc.). Avoids calling external programs if possible (e.g., prefers Bash built-ins). (Updated, thanks for the heads up, wich) It doesn't have to resolve symlinks (in fact, I'd kind of prefer it left them alone, but that's not a requirement).
Here's what I've come up with (edit: plus some tweaks provided by sfstewman, levigroker, Kyle Strand, and Rob Kennedy), that seems to mostly fit my "better" criteria: SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" That SCRIPTPATH line seems particularly roundabout, but we need it rather than SCRIPTPATH=`pwd` in order to properly handle spaces and symlinks. The inclusion of output redirection (>/dev/null 2>&1) handles the rare(?) case where cd might produce output that would interfere with the surrounding $( ... ) capture. (Such as cd being overridden to also ls a directory after switching to it.) Note also that esoteric situations, such as executing a script that isn't coming from a file in an accessible file system at all (which is perfectly possible), is not catered to there (or in any of the other answers I've seen). The -- after cd and before "$0" are in case the directory starts with a -.
I'm surprised that the realpath command hasn't been mentioned here. My understanding is that it is widely portable / ported. Your initial solution becomes: SCRIPT=$(realpath "$0") SCRIPTPATH=$(dirname "$SCRIPT") And to leave symbolic links unresolved per your preference: SCRIPT=$(realpath -s "$0") SCRIPTPATH=$(dirname "$SCRIPT")
The simplest way that I have found to get a full canonical path in Bash is to use cd and pwd: ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" Using ${BASH_SOURCE[0]} instead of $0 produces the same behavior regardless of whether the script is invoked as <name> or source <name>.
I just had to revisit this issue today and found Get the source directory of a Bash script from within the script itself: DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" There's more variants at the linked answer, e.g. for the case where the script itself is a symlink.
Get the absolute path of a shell script It does not use the -f option in readlink, and it should therefore work on BSD/Mac OS X. Supports source ./script (When called by the . dot operator) Absolute path /path/to/script Relative path like ./script /path/dir1/../dir2/dir3/../script When called from symlink When symlink is nested eg) foo->dir1/dir2/bar bar->./../doe doe->script When caller changes the scripts name I am looking for corner cases where this code does not work. Please let me know. Code pushd . > /dev/null SCRIPT_PATH="${BASH_SOURCE[0]}"; while([ -h "${SCRIPT_PATH}" ]); do cd "`dirname "${SCRIPT_PATH}"`" SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; done cd "`dirname "${SCRIPT_PATH}"`" > /dev/null SCRIPT_PATH="`pwd`"; popd > /dev/null echo "srcipt=[${SCRIPT_PATH}]" echo "pwd =[`pwd`]" Known issus The script must be on disk somewhere. Let it be over a network. If you try to run this script from a PIPE it will not work wget -o /dev/null -O - http://host.domain/dir/script.sh |bash Technically speaking, it is undefined. Practically speaking, there is no sane way to detect this. (A co-process can not access the environment of the parent.)
Use: SCRIPT_PATH=$(dirname `which $0`) which prints to standard output the full path of the executable that would have been executed when the passed argument had been entered at the shell prompt (which is what $0 contains) dirname strips the non-directory suffix from a file name. Hence you end up with the full path of the script, no matter if the path was specified or not.
As realpath is not installed per default on my Linux system, the following works for me: SCRIPT="$(readlink --canonicalize-existing "$0")" SCRIPTPATH="$(dirname "$SCRIPT")" $SCRIPT will contain the real file path to the script and $SCRIPTPATH the real path of the directory containing the script. Before using this read the comments of this answer.
Easy to read? Below is an alternative. It ignores symlinks #!/bin/bash currentDir=$( cd $(dirname "$0") pwd ) echo -n "current " pwd echo script $currentDir Since I posted the above answer a couple years ago, I've evolved my practice to using this linux specific paradigm, which properly handles symlinks: ORIGIN=$(dirname $(readlink -f $0))
Simply: BASEDIR=$(readlink -f $0 | xargs dirname) Fancy operators are not needed.
You may try to define the following variable: CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" Or you can try the following function in Bash: realpath () { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" } This function takes one argument. If the argument already has an absolute path, print it as it is, otherwise print $PWD variable + filename argument (without ./ prefix). Related: Bash script absolute path with OS X Get the source directory of a Bash script from within the script itself
Answering this question very late, but I use: SCRIPT=$( readlink -m $( type -p ${0} )) # Full path to script handling Symlinks BASE_DIR=`dirname "${SCRIPT}"` # Directory script is run in NAME=`basename "${SCRIPT}"` # Actual name of script even if linked
We have placed our own product realpath-lib on GitHub for free and unencumbered community use. Shameless plug but with this Bash library you can: get_realpath <absolute|relative|symlink|local file> This function is the core of the library: function get_realpath() { if [[ -f "$1" ]] then # file *must* exist if cd "$(echo "${1%/*}")" &>/dev/null then # file *may* not be local # exception is ./file.ext # try 'cd .; cd -;' *works!* local tmppwd="$PWD" cd - &>/dev/null else # file *must* be local local tmppwd="$PWD" fi else # file *cannot* exist return 1 # failure fi # reassemble realpath echo "$tmppwd"/"${1##*/}" return 0 # success } It doesn't require any external dependencies, just Bash 4+. Also contains functions to get_dirname, get_filename, get_stemname and validate_path validate_realpath. It's free, clean, simple and well documented, so it can be used for learning purposes too, and no doubt can be improved. Try it across platforms. Update: After some review and testing we have replaced the above function with something that achieves the same result (without using dirname, only pure Bash) but with better efficiency: function get_realpath() { [[ ! -f "$1" ]] && return 1 # failure : file does not exist. [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks. echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result. return 0 # success } This also includes an environment setting no_symlinks that provides the ability to resolve symlinks to the physical system. By default it keeps symlinks intact.
Considering this issue again: there is a very popular solution that is referenced within this thread that has its origin here: DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" I have stayed away from this solution because of the use of dirname - it can present cross-platform difficulties, particularly if a script needs to be locked down for security reasons. But as a pure Bash alternative, how about using: DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )" Would this be an option?
If we use Bash I believe this is the most convenient way as it doesn't require calls to any external commands: THIS_PATH="${BASH_SOURCE[0]}"; THIS_DIR=$(dirname $THIS_PATH)
The accepted solution has the inconvenient (for me) to not be "source-able": if you call it from a "source ../../yourScript", $0 would be "bash"! The following function (for bash >= 3.0) gives me the right path, however the script might be called (directly or through source, with an absolute or a relative path): (by "right path", I mean the full absolute path of the script being called, even when called from another path, directly or with "source") #!/bin/bash echo $0 executed function bashscriptpath() { local _sp=$1 local ascript="$0" local asp="$(dirname $0)" #echo "b1 asp '$asp', b1 ascript '$ascript'" if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}" elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd) else if [[ "$ascript" == "bash" ]] ; then ascript=${BASH_SOURCE[0]} asp="$(dirname $ascript)" fi #echo "b2 asp '$asp', b2 ascript '$ascript'" if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ; elif [[ "${ascript#../}" != "$ascript" ]]; then asp=$(pwd) while [[ "${ascript#../}" != "$ascript" ]]; do asp=${asp%/*} ascript=${ascript#../} done elif [[ "${ascript#*/}" != "$ascript" ]]; then if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi fi fi eval $_sp="'$asp'" } bashscriptpath H export H=${H} The key is to detect the "source" case and to use ${BASH_SOURCE[0]} to get back the actual script.
One liner `dirname $(realpath $0)`
Bourne shell (sh) compliant way: SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
Perhaps the accepted answer to the following question may be of help. How can I get the behavior of GNU's readlink -f on a Mac? Given that you just want to canonicalize the name you get from concatenating $PWD and $0 (assuming that $0 is not absolute to begin with), just use a series of regex replacements along the line of abs_dir=${abs_dir//\/.\//\/} and such. Yes, I know it looks horrible, but it'll work and is pure Bash.
Try this: cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
I have used the following approach successfully for a while (not on OS X though), and it only uses a shell built-in and handles the 'source foobar.sh' case as far as I have seen. One issue with the (hastily put together) example code below is that the function uses $PWD which may or may not be correct at the time of the function call. So that needs to be handled. #!/bin/bash function canonical_path() { # Handle relative vs absolute path [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1 # Change to dirname of x cd ${x%/*} # Combine new pwd with basename of x echo $(pwd -P)/${x##*/} cd $OLDPWD } echo $(canonical_path "${BASH_SOURCE[0]}") type [ type cd type echo type pwd
Just for the hell of it I've done a bit of hacking on a script that does things purely textually, purely in Bash. I hope I caught all the edge cases. Note that the ${var//pat/repl} that I mentioned in the other answer doesn't work since you can't make it replace only the shortest possible match, which is a problem for replacing /foo/../ as e.g. /*/../ will take everything before it, not just a single entry. And since these patterns aren't really regexes I don't see how that can be made to work. So here's the nicely convoluted solution I came up with, enjoy. ;) By the way, let me know if you find any unhandled edge cases. #!/bin/bash canonicalize_path() { local path="$1" OIFS="$IFS" IFS=$'/' read -a parts < <(echo "$path") IFS="$OIFS" local i=${#parts[#]} local j=0 local back=0 local -a rev_canon while (($i > 0)); do ((i--)) case "${parts[$i]}" in ""|.) ;; ..) ((back++));; *) if (($back > 0)); then ((back--)) else rev_canon[j]="${parts[$i]}" ((j++)) fi;; esac done while (($j > 0)); do ((j--)) echo -n "/${rev_canon[$j]}" done echo } canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
Yet another way to do this: shopt -s extglob selfpath=$0 selfdir=${selfpath%%+([!/])} while [[ -L "$selfpath" ]];do selfpath=$(readlink "$selfpath") if [[ ! "$selfpath" =~ ^/ ]];then selfpath=${selfdir}${selfpath} fi selfdir=${selfpath%%+([!/])} done echo $selfpath $selfdir
More simply, this is what works for me: MY_DIR=`dirname $0` source $MY_DIR/_inc_db.sh
Is there a Bash shortcut for traversing similar directory structures?
The KornShell (ksh) used to have a very useful option to cd for traversing similar directory structures; e.g., given the following directories: /home/sweet/dev/projects/trunk/projecta/app/models /home/andy/dev/projects/trunk/projecta/app/models Then if you were in the /home/sweet... directory then you could change to the equivalent directory in andy's structure by typing cd sweet andy So if ksh saw 2 arguments then it would scan the current directory path for the first value, replace it with the second and cd there. Is anyone aware of similar functionality built into Bash? Or if not, a hack to make Bash work in the same way?
Other solutions offered so far suffer from one or more of the following problems: Archaic forms of tests - as pointed out by Michał Górny Incomplete protection from directory names containing white space Failure to handle directory structures which have the same name used more than once or with substrings that match: /canis/lupus/lupus/ or /nicknames/Robert/Rob/ This version handles all the issues listed above. cd () { local pwd="${PWD}/"; # we need a slash at the end so we can check for it, too if [[ "$1" == "-e" ]] then shift # start from the end [[ "$2" ]] && builtin cd "${pwd%/$1/*}/${2:-$1}/${pwd##*/$1/}" || builtin cd "$#" else # start from the beginning [[ "$2" ]] && builtin cd "${pwd/\/$1\///$2/}" || builtin cd "$#" fi } Issuing any of the other versions, which I'll call cdX, from a directory such as this one: /canis/lupus/lupus/specimen $ cdX lupus familiaris bash: cd: /canis/familiaris/lupus/specimen: No such file or directory fails if the second instance of "lupus" is the one intended. In order to accommodate this, you can use the "-e" option to start from the end of the directory structure. /canis/lupus/lupus/specimen $ cd -e lupus familiaris /canis/lupus/familiaris/specimen $ Or issuing one of them from this one: /nicknames/Robert/Rob $ cdX Rob Bob bash: cd: /nicknames/Bobert/Rob: No such file or directory would substitute part of a string unintentionally. My function handles this by including the slashes in the match. /nicknames/Robert/Rob $ cd Rob Bob /nicknames/Robert/Bob $ You can also designate a directory unambiguously like this: /fish/fish/fins $ cd fish/fins robot/fins /fish/robot/fins $ By the way, I used the control operators && and || in my function instead of if...then...else...fi just for the sake of variety.
cd "${PWD/sweet/andy}"
No, but... Michał Górny's substitution expression works nicely. To redefine the built-in cd command, do this: cd () { if [ "x$2" != x ]; then builtin cd ${PWD/$1/$2} else builtin cd "$#" fi }