How to see what /bin/sh points to - bash

I was reading about the differences between /bin/sh and /bin/bash and came across this interesting question/answer: here
I made a comment to the answer asking this same question in my title to which he replied with an updated answer:
How can you find out what /bin/sh points to on your system?
The complication is that /bin/sh could be a symbolic link or a hard
link. If it's a symbolic link, a portable way to resolve it is:
> % file -h /bin/sh
> /bin/sh: symbolic link to bash
If it's a hard link,
try
> % find -L /bin -samefile /bin/sh
> /bin/sh
> /bin/bash
I tried this and had trouble so I thought I would make a separate question.
My results from the linked answer:
>file -h /bin/sh
>/bin/sh: executable (RISC System/6000) or object module
>find -L /bin -samefile /bin/sh
>find: bad option -samefile
What am I missing?
I'm running AIX:
>oslevel -s
7100-03-03-1415

If you need to programatically test if they are the same, you can use stat to query the inode of /bin/sh and compare with the inode of /bin/bash.
if [ $(stat -L -c %i /bin/sh) -eq $(stat -L -c %i /bin/bash) ]; then
.....
fi
If you just need to see with your eyes if they are the same run the stat commands and see if they return the same inode number.
stat -L -c %i /bin/sh
stat -L -c %i /bin/bash

Since you are only searching through bin anyway, you can bypass find entirely and just check if sh and bash are hard links to the same file:
test /bin/sh -ef /bin/bash
OR
[ /bin/sh -ef /bin/bash ]
This is not as reliable as running find on all the possibilities, but it's a good start. While AIX find doesn't support -samefile, it does support -exec, which can be combined with the command above to simulate the same functionality:
find -L /bin -exec test /bin/sh -ef '{}' ';'

Check for GNU Bash
I'm going to answer your question in a different way, because it's actually simpler to find out if sh is GNU Bash (or something else that responds to a --version flag) than it is to chase inodes. There's also the edge case where the shell is renamed rather than linked, in which case mapping links won't really help you find an answer.
For example, to interrogate /bin/sh on macOS:
$ /bin/sh --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
Alternatively, you can grep (or similar) for the string bash to capture the exit status. For example:
# Close stderr in case sh doesn't have a version flag.
if sh --version 2>&- | grep -qF bash; then
echo "sh is bash"
else
echo "sh isn't bash"
fi
Of course, /bin/sh could be some other shell besides bash or the original bourne shell, but that's outside the scope of your original question. However, many shells such as ksh and tcsh also support the version flag, so judicious use of a case statement could help you extend the test to determine exactly which shell binary /bin/sh really is.

As the answer you commented on says, -samefile isn't a standard feature of find. Neither is -inum, which searches based on an explicitly given inode number. But if your find supports that, use it.
POSIX ls supports -i, which prints the inode numbers. If find doesn't have -inum, all you need to do is go through all plausible files /bin/sh could be a hard link with...
Though before that, you could check if the file even has any other hard links, ls -l is required to show the number of links:
$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1029624 Nov 5 23:22 /bin/bash
^ here
Of course, there's no requirement for /bin/sh to be linked to anything. It could be just another program. (Or even an identical copy of some of other file, though I don't think that's very likely.)

You can just use :
ls -l /bin/sh

You can also do readlink -f /bin/sh. For me it points to /bin/dash.

I can not really see the point of this exercise.
/bin/sh is a POSIX shell and bash has extensions to the POSIX standard that /bin/sh does not support.
If you are writing a POSIX shell script, use /bin/sh. If you are writing a bash shell script, use bash.
Do not try to write a bash script that will be executed with /bin/sh, and don't try to programatically try to determine the abilities of the current shell based on what /bin/sh (or the current interpreter) is linked to. Instead, make the script executable and use the correct #!-line for the script.

Related

`set -o posix` Not Working in Bash 5.0.17 on WSL2 Ubuntu 20.04

As mentioned in the title, the set -o posix option does not appear to be working in GNU/Linux bash 5.0.17 (I'm not sure it's specifically related to WSL2 Ubuntu 20.04 or anything, but noted it in case it is working for others on their machines).
I can turn it on and off:
$ set -o |grep posix
posix off
$ set -o posix
$ set -o |grep posix
posix on
$ set -o posix
$ set -o |grep posix
posix off
$ set -o posix
$ set -o |grep posix
posix on
but, for example, when on I am able to do the following
$ set -o posix
$ set -o |grep posix
posix on
$ type cd
cd is a shell builtin
$ cd /
$ ls
bin dev home lib lib64 lost+found mnt proc run snap sys usr
boot etc init lib32 libx32 media opt root sbin srv tmp var
$ cd() { :; }
$ type cd
cd is a function
cd ()
{
:
}
$ cd ~
$ ls
bin dev home lib lib64 lost+found mnt proc run snap sys usr
boot etc init lib32 libx32 media opt root sbin srv tmp var
$ unset -f cd
$ cd ~
$ ls
Desktop Documents Downloads Music Pictures Public Templates Videos
I am able to override all special builtins (including builtin and type!). Also, I am not root, but just in my user account. Does anyone know what I am doing wrong?
UPDATE:
#Shawn, #oguzismail, and #Dan all give the correct answer below: cd is a regular builtin. I wanted to add some clarification as I think it will be important for learners like me:
#oguzismail correctly points to the section of Classic Shell Scripting that explains that cd is a regular built-in:
For this reason, you can write a function named cd, and the shell will
find your function first, since cd is a regular built-in
Section 7.9 (pg. 261 in Kindle Edition)
My confusion came from the statement in the summary:
Built-in commands exist either because they change the shell’s
internal state and must be built-in (such as cd), or for efficiency
(such as test).
(Section 7 Summary, pg. 264 in Kindle Edition)
Here, cd is a builtin, but as the author states previously, it is a regular builtin. It has to be a command built into the shell because the command changes the internal state of the shell (i.e. it changes the current/present working directory of the shell), but it can be either a regular or special builtin. As #Dan mentions, the POSIX IEEE Std 1003.1-2017 standard, Section 2.14 defines it as a regular builtin. (For at least one reason why, Section 7.9 of Classic Shell Scripting shows that doing so permits the programmer to customize its behavior.)
Be Warned Though! (NB = Nota Bene): If you do choose to overwrite cd with a user-defined function, use command cd within the function definition where you want to actually change directories. The function command skips looking for functions in the POSIX-defined search order of special built-ins --> functions --> regular built-ins --> external commands on $PATH. Otherwise, if you use cd within your user-defined function cd, you will most likely end up with an infinite recursion loop. (Classic Shell Scripting also covers this point.)
The key here is what bash considers a special builtin to be. I had to dig into the source to find a list:
/* The Posix.2 so-called `special' builtins. */
char *special_builtins[] =
{
":", ".", "source", "break", "continue", "eval", "exec", "exit",
"export", "readonly", "return", "set", "shift", "times", "trap", "unset",
(char *)NULL
};
They're also described in the POSIX documentation as being distinct from regular built-ins.
And indeed...
$ bash --posix
bash-4.4$ break() { :; }
bash: `break': is a special builtin
bash-4.4$ type break
break is a special shell builtin
bash-4.4$ type cd
cd is a shell builtin
Note the different output from type for break and cd, because the latter isn't in that list.
set -o posix works fine:
$ set -o posix
$ set () { :; }
bash: `set': is a special builtin
cd is not a special built in, nor is builtin or type.
The list of "special builtins" is clearly defined in the posix specification.
A "special built-in" is distinct from any other command which a shell may or may not implement as a built-in.

Why does type ls output ls is aliased to `ls --color=auto'?

I'm learning bash, so this might be a very noobish question, but I don't understand why type ls give me the output ls is aliased to 'ls --color=auto', instead of ls is /bin/ls which is what I expected.
I haven't changed any settings that could have an impact on this, as far as I know. I'm running my bash commands in an Ubuntu 16.04.2 LTS environment through Vagrant.
Very likely because ls is aliased to 'ls --color=auto'. Did you check your .bashrc?
grep alias .bashrc
will give you all the relevant lines.
Remark: you can have all the possible aliases, builtins, functions, etc. by using the -a option to type:
type -a ls
will certainly also give you /bin/ls.

How do I change Korn(ksh) version dynamically based on platform?

I wanted to use the /usr/bin/ksh93 interpreter on AIX and Linux wherever possible but switch to /usr/bin/ksh where it's not applicable like Mac OS X and wanted the script to be universally compatible in unix. I don't think there is any fallback mechanism in shebang
Since ksh and sh have some syntax in common, you can prefix the start of the
script with a test for ksh or ksh93 in the PATH and rerun the script with
the right interpreter. Replace the #! with the pathname to sh. (Hopefully
it is the same on both machines, or you are back where you started. You can
still try #!/usr/bin/env sh if your env will find the path for you). Add:
#!/bin/sh
if [ "$DONEIT" != true ]
then export DONEIT=true # avoid recursion
if command -v ksh > /dev/null 2>&1
then exec ksh $0 "$#"
else exec ksh93 $0 "$#"
fi
fi
... rest of your script ...
Note: command -v is the POSIX way for finding a command's path.
(Often in these situations, at the installation of a package a script goes
through the #! files and updates the interpreter path to that needed by the
target machine).
Alternatively, you could replace the #! line by any fixed path you control, eg #!/home/user/myksh, and link that file to the right ksh.
You can make a symbolic links.
if [ -f /usr/bin/ksh93 ]; then
ln -s /usr/bin/ksh93 /usr/bin/localksh
else
ln -s /usr/bin/ksh /usr/bin/localksh
fi
The shebang will be #!/usr/bin/localksh.
I would prefer using a normal shebang #!/bin/ksh, but when that one already exists and is the wrong version you will be stuck.

Get current directory and concatenate a path

This is a shell script (.sh file). I need to create an absolute path based on the current directory. I know about pwd, but how do I concatenate it with another string? Here is an example of what I am trying to do:
"$pwd/some/path"
Sounds like you want:
path="$(pwd)/some/path"
The $( opens a subshell (and the ) closes it) where the contents are executed as a script so any outputs are put in that location in the string.
More useful often is getting the directory of the script that is running:
dot="$(cd "$(dirname "$0")"; pwd)"
path="$dot/some/path"
That's more useful because it resolves to the same path no matter where you are when you run the script:
> pwd
~
> ./my_project/my_script.sh
~/my_project/some/path
rather than:
> pwd
~
> ./my_project/my_script.sh
~/some/path
> cd my_project
> pwd
~/my_project
> ./my_script.sh
~/my_project/some/path
More complex but if you need the directory of the current script running if it has been executed through a symlink (common when installing scripts through homebrew for example) then you need to parse and follow the symlink:
if [[ "$OSTYPE" == *darwin* ]]; then
READLINK_CMD='greadlink'
else
READLINK_CMD='readlink'
fi
dot="$(cd "$(dirname "$([ -L "$0" ] && $READLINK_CMD -f "$0" || echo "$0")")"; pwd)"
More complex and more requirements for it to work (e.g. having a gnu compatible readlink installed) so I tend not to use it as much. Only when I'm certain I need it, like installing a command through homebrew.
Using the shell builtin pwd in a command substitution ($(...)) is an option, but not necessary, because all POSIX-compatible shells define the special $PWD shell variable that contains the current directory as an absolute path, as mandated by POSIX.
Thus, using $PWD is both simpler and more efficient than $(pwd):
"$PWD/some/path" # alternatively, for visual clarity: "${PWD}/some/path"
However, if you wanted to resolve symlinks in the directory path, you DO need pwd, with its -P option:
"$(pwd -P)/some/path"
Note that POSIX mandates that $PWD contain an absolute pathname with symlinks resolved.
In practice, however, NO major POSIX-like shell (bash, dash, ksh, zsh) does that - they all retain symbolic link components. Thus, the (POSIX-compliant) pwd -P is needed to resolve them.
Note that all said POSIX-like shells implement pwd as a builtin that supports -P.
Michael Allen's helpful answer points out that it's common to want to know the directory of where the running script is located.
The challenge is that the script file itself may be a symlink, so determining the true directory of origin is non-trivial, especially when portability is a must.
This answer (of mine) shows a solution.
wd=`pwd`
new_path="$wd/some/path"
with "dirname $0" you can get dynamin path upto current run scipt.
for example : your file is locateted in shell folder file name is xyz and there are anthor file abc to include in xyz file.
so put in xyz file LIke:
php "`dirname $0`"/abc.php

How to find out where alias (in the bash sense) is defined when running Terminal in Mac OS X

How can I find out where an alias is defined on my system? I am referring to the kind of alias that is used within a Terminal session launched from Mac OS X (10.6.3).
For example, if I enter the alias command with no parameters at a Terminal command prompt, I get a list of aliases that I have set, for example:
alias mysql='/usr/local/mysql/bin/mysql'
However, I have searched all over my system using Spotlight and mdfind in various startup files and so far can not find where this alias has been defined. ( I did it a long time ago and didn't write down where I assigned the alias).
For OSX, this 2-step sequence worked well for me, in locating an alias I'd created long ago and couldn't locate in expected place (~/.zshrc).
cweekly:~ $ which la
la: aliased to ls -lAh
cweekly:~$ grep -r ' ls -lAh' ~
/Users/cweekly//.oh-my-zsh/lib/aliases.zsh:alias la='ls -lAh'
Aha! "Hiding" in ~/.oh-my-zsh/lib/aliases.zsh. I had poked around a bit in .oh-my-zsh but had overlooked lib/aliases.zsh.
you can just simply type in alias on the command prompt to see what aliases you have. Otherwise, you can do a find on the most common places where aliases are defined, eg
grep -RHi "alias" /etc /root
First use the following commands
List all functions
functions
List all aliases
alias
If you aren't finding the alias or function consider a more aggressive searching method
Bash version
bash -ixlc : 2>&1 | grep thingToSearchHere
Zsh version
zsh -ixc : 2>&1 | grep thingToSearchHere
Brief Explanation of Options
-i Force shell to be interactive.
-c Take the first argument as a command to execute
-x -- equivalent to --xtrace
-l Make bash act as if invoked as a login shell
Also in future these are the standard bash config files
/etc/profile
~/.bash_profile or ~/.bash_login or ~/.profile
~/.bash_logout
~/.bashrc
More info: http://www.heimhardt.com/htdocs/bashrcs.html
A bit late to the party, but I was having the same problem (trying to find where the "l." command was aliased in RHEL6), and ended up in a place not mentioned in the previous answers. It may not be found in all bash implementations, but if the /etc/profile.d/ directory exists, try grepping there for unexplained aliases. That's where I found:
[user#server ~]$ grep l\\. /etc/profile.d/*
/etc/profile.d/colorls.csh:alias l. 'ls -d .*'
/etc/profile.d/colorls.csh:alias l. 'ls -d .* --color=auto'
/etc/profile.d/colorls.sh: alias l.='ls -d .*' 2>/dev/null
/etc/profile.d/colorls.sh:alias l.='ls -d .* --color=auto' 2>/dev/null
The directory isn't mentioned in the bash manpage, and isn't properly part of where bash searches for profile/startup info, but in the case of RHEL you can see the calling code within /etc/profile:
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null 2>&1
fi
fi
done
Please do check custom installations/addons/plugins you have added, in addition to the .zshrc/.bashrc/.profile etc files
So for me: it was git aliased to 'g'.
$ which g
g: aliased to git
Then I ran the following command to list all aliases
$ alias
I found a whole lot of git related aliases that I knew I had not manually added.
This got me thinking about packages or configurations I had installed. And so went to the
.oh-my-zsh
directory. Here I ran the following command:
$ grep -r 'git' . |grep -i alias
And lo and behold, I found my alias in :
./plugins/git/git.plugin.zsh
I found the answer ( I had been staring at the correct file but missed the obvious ).
The aliases in my case are defined in the file ~/.bash_profile
Somehow this eluded me.
For more complex setups (e.g. when you're using a shell script framework like bash-it, oh-my-zsh or the likes) it's often useful to add 'alias mysql' at key positions in your scripts. This will help you figure out exactly when the alias is added.
e.g.:
echo "before sourcing .bash-it:"
alias mysql
. $HOME/.bash-it/bash-it.sh
echo "after sourcing bash:"
alias mysql
I think that maybe this is similar to what ghostdog74 meant however their command didn't work for me.
I would try something like this:
for i in `find . -type f`; do # find all files in/under current dir
echo "========"
echo $i # print file name
cat $i | grep "alias" # find if it has alias and if it does print the line containing it
done
If you wanted to be really fancy you could even add an if [[ grep -c "alias" ]] then <print file name>
The only reliable way of finding where the alias could have been defined is by analyzing the list of files opened by bash using dtruss.
If
$ csrutil status
System Integrity Protection status: enabled.
you won't be able to open bash and you may need a copy.
$ cp /bin/bash mybash
$ $ codesign --remove-signature mybash
and then use
sudo dtruss -t open ./mybash -ic exit 2>&1 | awk -F'"' '/^open/ {print substr($2, 0, length($2)-2)}'
to list all the files where the alias could have been defined, like
/dev/dtracehelper
/dev/tty
/usr/share/locale/en_CA.UTF-8/LC_MESSAGES/BASH.mo
/usr/share/locale/en_CA.utf8/LC_MESSAGES/BASH.mo
/usr/share/locale/en_CA/LC_MESSAGES/BASH.mo
/usr/share/locale/en.UTF-8/LC_MESSAGES/BASH.mo
/usr/share/locale/en.utf8/LC_MESSAGES/BASH.mo
/usr/share/locale/en/LC_MESSAGES/BASH.mo
/Users/user/.bashrc
/Users/user/.bash_aliases
/Users/user/.bash_history
...
Try: alias | grep name_of_alias
Ex.: alias | grep mysql
or, as already mentioned above
which name_of_alias
In my case, I use Oh My Zsh, so I put aliases definition in ~/.zshrc file.

Resources