(Since I couldn't find an explicit answer to this anywhere and I find it useful, I though I would add it to SO. Better alternatives welcome.)
In Bash, how do you alias cd to echo the new working directory after a change? Like this:
$ pwd
/some/directory
$ cd newness
/some/directory/newness
$
Something simple like alias cd='cd "$#" && pwd' doesn't work. For some reason, Bash responds as though you used cd - and returns to $OLDPWD and you get caught in a loop. I don't understand this behavior.
Apparently, you need to do this through a function:
function mycd() {
cd "$#" && pwd
}
alias cd=mycd
However, if you use cd - you wind up printing the directory twice, so this is more robust:
function mycd() {
if [ "$1" == "-" ]; then
cd "$#"
else
cd "$#" && pwd
fi
}
alias cd=mycd
I may be missing some edge cases, like cd -P - and cd -L -, though I don't know if those even make sense.
(See Adrian Frühwirth's answer below for the reason why a simple alias doesn't work and why I feel dumb now.)
The alias doesn't work because aliases don't support arguments, so you cannot chain commands that require parameters. Because of this cd "$#" doesn't make sense in the context of an alias and whatever argument you supply to an alias just gets appended:
$ alias foo='echo "[$#]" && echo'
$ foo bar
[]
bar
Obviously, that's not what you want and exactly why you need to resort to a function.
Related
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.
I'm making a few aliases to speed up my development processes.
Right now I'm trying to make a cdls which is quite obviously a cd {arbitrary-file} && ls {arbitrary-file}
I was under the impression that alias cdls='cd $# && ls $# would work, but it looks like I was mistaken about $# carrying the argument (file path) since this sends me back to my $HOME directory every time.
Aliases don't process arguments. Use a function instead:
cdls () {
cd "$1"
ls
}
Try instead in .bash_profile
function cdls () { ls "$#" && eval cd "\"\$$#\"";}
I don't remember where I got that from but I have something similar to it working.
Generally I keep directory specific settings in .bashrc and whenever I change directory execute the command source .bashrc to make those settings effective.
Now I was thinking of manipulating cd command in ~/.bashrc, so whenever I cd to new directory and if any .bashrc exists there, it will be loaded automatically.
Similar to this cd $1; source .bashrc ( I have verified that $1 is valid path), but problem is cd is shell builting, so it's a recursive loop ( cd always points to modifed cd ). We do not have elf file of cd ( which generally we have of other commands viz scp or others). So how can I achieve this ?
Also if shopt -s cdspell is supported then also I need to have cd spelled path in argument of $1.
You want the "builtin" command;
builtin shell-builtin [arguments]
Execute the specified shell builtin,
passing it arguments, and return its exit status. This is useful when
defining a function whose name is the same as a shell builtin,
retaining the functionality of the builtin within the function. The cd
builtin is commonly redefined this way. The return status is false if
shell-builtin is not a shell builtin command.
From: http://linux.die.net/man/1/bash
So, you could have something like (untested, don't have a bash handy either);
function cd() {
builtin cd $1 \
&& test -e .bashrc \
&& source .bashrc
}
You might check out direnv. https://github.com/zimbatm/direnv
RVM does this:
$ type cd
cd is a function
cd ()
{
if builtin cd "$#"; then
[[ -n "${rvm_current_rvmrc:-}" && "$*" == "." ]] && rvm_current_rvmrc="" || true;
__rvm_do_with_env_before;
__rvm_project_rvmrc;
__rvm_after_cd;
__rvm_do_with_env_after;
return 0;
else
return $?;
fi
}
And yes, this works on my machine. Essentially, as #RoryHunter said, use builtin and run some code if it succeeds, or return the exit code if it fails.
You could try this:
function cdd(){ cd $1; if [ -e ./.bashrc ] ; then source ./.bashrc; fi; }
alias cd = 'cdd'
?
Didn't tested this much, however.
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
}
I often want to change to the directory where a particular executable is located. So I'd like something like
cd `which python`
to change into the directory where the python command is installed. However, this is obviously illegal, since cd takes a directory, not a file. There is obviously some regexp-foo I could do to strip off the filename, but that would defeat the point of it being an easy one-liner.
Here:
cd $(dirname `which python`)
Edit:
Even easier (actually tested this time):
function cdfoo() { cd $(dirname `which $#`); }
Then "cdfoo python".
To avoid all those external programs ('dirname' and far worse, the useless but popular 'which') maybe a bit rewritten:
cdfoo() {
tgtbin=$(type -P "$1")
[[ $? != 0 ]] && {
echo "Error: '$1' not found in PATH" >&2
return 1
}
cd "${tgtbin%/*}"
}
This also fixes the uncommon keyword 'function' from above and adds (very simple) error handling.
May be a start for a more sphisticated solution.
For comparison:
zsh:~% cd =vi(:h)
zsh:/usr/bin%
=cmd expands to the path to cmd and (:h) is a glob modifier to take the head
zsh is write-only but powerful.
something like that should do the trick :
cd `dirname $(which python)`
One feature I've used allot is pushd / popd. These maintain a directory stack so that you don't have to try to keep history of where you were if you wish to return to the current working directory prior to changing directories.
For example:
pushd $(dirname `which $#`)
...
popd
You could use something like this:
cd `which <file> | xargs dirname`
I added a bit of simple error handling that makes the behavior of cdfoo() follow that of dirname for nonexistent/nonpath arguments
function cdfoo() { cd $(dirname $(which $1 || ( echo . && echo "Error: '$1' not found" >&2 ) ));}