What is the difference between tild and eval to find user home - bash

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

Related

Expand variable before tilde

I'm sure this has been asked before but I can't find anything. We have inscrutable login names on a shared machine and want to use shell variables to substitute the hard-to-remember login names for people's real names.
For example, let's say Omar's login name is xyz123. I can do this:
$ omar=xyz123
$ echo ~$omar
and output looks fine:
~xyz123
but if I type this:
$ ls ~$omar
there is an error:
ls: cannot access ~xyz123: No such file or directory
I think it's because tilde expansion happens before variable expansion but can't figure out how to get around this.
Perhaps this answer is related although I'm not sure:
How to manually expand a special variable (ex: ~ tilde) in bash
bash expands the tilde before the variable. See https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions
The shell will see if the literal characters $ o m a r are a login name. As they are not, the tilde is not expanded. The shell eventually sees $omar as a variable and substitutes that. It then hands the expanded word ~xyz123 to echo which just prints it.
Similarly, it hands the word ~xyz123 to ls. Since ls does not do its own tilde expansion, it is looking for a file in your current directory named ~xyz123 with a literal tilde. Since such a file does not exist you get that error.
If you want ls ~$var to list files, you need eval ls ~$var. Or, since eval is considered very unsafe to use casually, you could do this instead:
ls "$(getent passwd "$omar" | cut -d: -f6)"
I would go with checking if "$omar" is a valid user with id and then using eval to force double expansion. So protect against evil eval and then do it.
if ! id "$omar" >/dev/null 2>&1;
echo "Error: user with the name $omar does not exist!" >&2
exit 1
fi
eval echo "\"~$omar\""

pushd "no such file or directory" only sometimes [duplicate]

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"

Bash custom function to change directory [duplicate]

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"

bash string explained as an variable

I have a string in a bash script
foo="~/mydir" (not foo=~/mydir)
then i need to create this directory using
mkdir ${foo}
is it any way the foo can be explained as
<"user's home directory">/mydir
but not
<"current work directory">/~/mydir.
As pointed out, you can use $HOME to indicate the user's home directory.
But beware:
foo="~/mydir"
does not expand the tilde, so "echo $foo" still results in "~/mydir". If you want to expand the tilde, you can use
eval foo=$foo
Now "echo $foo" results for example in "/home/fred/mydir".
The tilde expansion is a little special, it is not a simple substitute for $HOME; look at the following:
foo="~/mydir"
echo ${foo/#\~/$HOME} (reply: /home/administrator/mydir)
eval foo=$foo
echo $foo (reply: /home/administrator/mydir)
---
foo="~administrator/mydir"
echo ${foo/#\~/$HOME} (reply: /home/administratoradministrator/mydir)
eval foo=$foo
echo $foo (reply: /home/administrator/mydir)
In other words: if you have always the simple form "~/...", substituting the tilde with $HOME works well. If you have the more advanced format, then eval is better, as you can see in the second example above.
The simple "eval foo=$foo" has problems with spaces in filenames:
foo="~administrator/mydir with spaces"
eval foo=$foo (error: with: command not found)
The following syntax is better:
foo2=$(eval echo $foo)
echo $foo2 (ok: /home/administrator/mydir with space)
Hope it helps.
You can delay using the tilde until when you need to create the directory :
foo="mydir"
....
mkdir ~/"$foo"
The tilde expands to the home directory only if unquoted.
Relevant section of the Bash manual:
https://www.gnu.org/software/bash/manual/bashref.html#Tilde-Expansion

~ not working from read command [duplicate]

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"

Resources