In tcsh I can extract second path element from the end of path by following way
cd /some/long/directory/structure/path/
set x=`pwd`
echo ${x:h:h:t}
directory
How can I do the same in bash?
I mean , does bash also have this kind of modifiers?
The csh-style modifiers can be used with history expansion (unsurprisingly, because history expansion was borrowed from csh).
$ cd /some/long/directory/structure/path/
$ echo !!:1:h:h:t
echo directory
directory
!!:1 selects word 1 (counting from zero) of the previous command, so the argument to cd.
(echo directory appears on standard error because the shell defaults to displaying the result of history expansion before actually executing the resulting command.)
In a non-interactive bash script, history expansion commands as in #chepner's answer won't normally be available. However, you do have parameter expansions like:
$ cd /some/long//directory///structure/path/
$ set x=$(pwd)
$ echo $x
/some/long/directory/structure/path
$ y=${y%/*/*} # each /* is equivalent to one :h
$ y=${y##*/} # equivalent to :t
$ echo $y
directory
cd /some/long/path/somewhere
x=$PWD
basename "$(dirname "$x")"
> path
dirname gets the absolute path of the parent folder of the argument. basename gets the name of the argument.
Edit: remembered the much better way than I was doing before.
Related
What is the difference between these two commands to find the user home-
$(eval echo ~<username>)
echo ~/
are there any scenario when both return different results?
Tilde expansion is done by the shell before executing the command. So in both cases, the home directory becomes the argument to echo.
eval re-evaluates its arguments. So if the home directory contains any characters that have special meaning to the shell, these will be interpreted. For instance, if the user's home directory were /home/$foo,
echo ~username
would display the pathname with the literal $foo in it, but
eval echo ~username
would replace $foo with the value of the foo variable.
Next, putting $() around a command means that the output of the command is substituted into the command line, and then the command line is executed. So
$(echo ~username)
$(eval echo ~username)
will both try to execute the home directory as a command, which will get an error because directories aren't executable programs. But if you meant that this is being used as an argument, e.g.
cd $(echo ~username)
vs
cd ~username
There should be little difference. The only difference would be if the home directory pathname contains whitespace or wildcard characters, because these are processed after $() substitution. This problem can be avoided by quoting:
cd "$(echo ~username)"
Say I have a folder called Foo located in /home/user/ (my /home/user also being represented by ~).
I want to have a variable
a="~/Foo" and then do
cd $a
I get
-bash: cd: ~/Foo: No such file or directory
However if I just do cd ~/Foo it works fine. Any clue on how to get this to work?
You can do (without quotes during variable assignment):
a=~/Foo
cd "$a"
But in this case the variable $a will not store ~/Foo but the expanded form /home/user/Foo. Or you could use eval:
a="~/Foo"
eval cd "$a"
You can use $HOME instead of the tilde (the tilde is expanded by the shell to the contents of $HOME).
Example:
dir="$HOME/Foo";
cd "$dir";
Although this question is merely asking for a workaround, this is listed as the duplicate of many questions that are asking why this happens, so I think it's worth giving an explanation. According to https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06:
The order of word expansion shall be as follows:
Tilde expansion, parameter expansion, command substitution, and arithmetic expansion shall be performed, beginning to end.
When the shell evaluates the string cd $a, it first performs tilde expansion (which is a no-op, since $a does not contain a tilde), then it expands $a to the string ~/Foo, which is the string that is finally passed as the argument to cd.
A much more robust solution would be to use something like sed or even better, bash parameter expansion:
somedir="~/Foo/test~/ing";
cd "${somedir/#\~/$HOME}"
or if you must use sed,
cd $(echo "$somedir" | sed "s#^~#$HOME#")
If you use double quotes the ~ will be kept as that character in $a.
cd $a will not expand the ~ since variable values are not expanded by the shell.
The solution is:
eval "cd $a"
Say I have a folder called Foo located in /home/user/ (my /home/user also being represented by ~).
I want to have a variable
a="~/Foo" and then do
cd $a
I get
-bash: cd: ~/Foo: No such file or directory
However if I just do cd ~/Foo it works fine. Any clue on how to get this to work?
You can do (without quotes during variable assignment):
a=~/Foo
cd "$a"
But in this case the variable $a will not store ~/Foo but the expanded form /home/user/Foo. Or you could use eval:
a="~/Foo"
eval cd "$a"
You can use $HOME instead of the tilde (the tilde is expanded by the shell to the contents of $HOME).
Example:
dir="$HOME/Foo";
cd "$dir";
Although this question is merely asking for a workaround, this is listed as the duplicate of many questions that are asking why this happens, so I think it's worth giving an explanation. According to https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06:
The order of word expansion shall be as follows:
Tilde expansion, parameter expansion, command substitution, and arithmetic expansion shall be performed, beginning to end.
When the shell evaluates the string cd $a, it first performs tilde expansion (which is a no-op, since $a does not contain a tilde), then it expands $a to the string ~/Foo, which is the string that is finally passed as the argument to cd.
A much more robust solution would be to use something like sed or even better, bash parameter expansion:
somedir="~/Foo/test~/ing";
cd "${somedir/#\~/$HOME}"
or if you must use sed,
cd $(echo "$somedir" | sed "s#^~#$HOME#")
If you use double quotes the ~ will be kept as that character in $a.
cd $a will not expand the ~ since variable values are not expanded by the shell.
The solution is:
eval "cd $a"
I know about escaping, quoting and stuff, but still have a problem.
I you have a script containing "cd $1", and call it with an argument containing spaces, cd will always return an error message - it stops at the first space and can't find the directory. I tried protecting the arguments in every way :
ls -l
+-rwx... script
+drwx... dir with spaces/
cat script
+cd $1
script dir with spaces
+cd: dir: no such file or directory
script "dir with spaces"
+cd: dir: no such file or directory
script dir\ with\ spaces
+cd: dir\: no such file or directory
but none will work.
I feel like I'm missing the obvious, thanks for enlightening me.
You need to quote the expansion of "$1" to prevent it from being word split as well as quoting the string passed to the script to prevent it from being word-split.
So
$ cat script.sh
cd -- "$1"
$ ./script.sh "dir with spaces"
Edit: As gniourf_gniourf correctly pointed out using -- as the first argument to cd prevents problems should paths ever start with -.
Use double quotes on the variable
cd "$1"
I am working on a Mac. I have a directory called 1. A (with a white space in the name) inside the directory Test
Now, the following ksh script
typeset MyPath=1.*
print $MyPath
cd $MyPath
pwd
cd ..
touch $MyPath/File.txt
produces
1. A
Test/1. A
touch: 1.*/File.txt: No such file or directory
Thus it looks like both print and cd recognize the pattern "1. A" but touch does not. Why?
Always quote your variables unless you want the expansion to be split on spaces and wilcards to be expanded.
MyPath=$(IFS= echo 1.*)
print "$MyPath"
cd "$MyPath"
pwd
cd ..
touch "$MyPath/File.txt"
echo 1.* expands the wildcard. The assignment IFS= disables word splitting by clearing the field separator variable (this is needed in case the name has multiple consecutive spaces, otherwise they would be merged).