How to check for path expansion for executables - bash

Like most makefiles, I have some bash scripts that use variables for executables. For example, $mysql_exec for /usr/bin/mysql. I would like to be able to say something like:
mysql_exec=mysql
to use $PATH or
mysql_exec=/usr/bin/mysql
to get an absolute location without $PATH. I also want to check to see if the executable is valid using something like this:
if [ ! -x $mysql_exec ] ...
However, this command:
if [ ! -x "mysql" ]; then print "oh oh"; fi
Actually prints "oh oh", even though mysql is in my $PATH. From the command-line, typing mysql has the same effect as typing /usr/bin/mysql. So, how do I get bash really check to see if $mysql_exec is an executable ($PATH and all)?

In Bash, you can use the built-in type -P to force a resolution against the PATH or type -p to show the path only if there's no alias or function by that name. Using type avoids calling an external such as which.
Something like this may do what you're looking for:
[ -x "$(type -p "$mysql_exec")" ]
whether you use
mysql_exec=mysql
or
mysql_exec=/usr/bin/mysql

which is a small program that checks $PATH for a program specified as an argument:
$ which mysql
/usr/bin/mysql
Another useful utility (that isn't installed on all systems but is included in GNU CoreUtils) is readlink. readlink can give you back a full and absolute path, without symlinks. For example:
$ cd ~me/bin
$ ln -s `which mysql` mysupersql
$ readlink -f mysupersql
/usr/bin/mysql
I often use a combination of both so I know that the path is both absolute and not a symlink:
mysql_exec=$(readlink -f `which mysql`)
if [ ! -x "$mysql_exec" ] ; then
...

To add to Kaleb's answer, you an also check if the file is a symlink using if [-L $file] and follow that symlink if you can't install readlink. The rest of the check though remains as the way Kaleb mentioned.

Related

How to get readlink -e on M1 Macs which also works when used in bash scripts?

I am using several bash scripts for build and deployment processes which use readlink with the -e option. Since this option is not available I followed this suggest to install coreutils and create a symbolink between greadlink and readlink.
This worked perfectly on my Intel mac but when I recently switch to M1 mac I realized that the path to greadlink and readlink are changed so I tried this:
ln -s /opt/homebrew/bin/greadlink /usr/bin/readlink
Which gave me an error: Operation not permitted
I realised that this is because of the System Integrity Protection
How can I still use readlink -e in my bash scripts without deactivate the System Integrity Protection?
One approach is to create a script named readlink somewhere in your PATH with the following content.
#!/bin/sh
exec greadlink "$#"
Just make sure that the relative path of script named readlink comes before /usr/bin/ since the system readlink is in /usr/bin when you run:
declare -p PATH
or
echo "$PATH"
An example how to do it:
Create a directory in ~/, name it scripts since it will have script as contents.
mkdir -p ~/scripts
Edit ~/.bashrc to include the created directory in the PATH env variable.
if [[ :$PATH: != *:$HOME/scripts:* ]]; then
PATH=$HOME/scripts:$PATH
fi
Source ~/.bashrc
source ~/.bashrc
Create a script name readlink inside the ~/scripts directory with the following contents:
#!/bin/sh
exec /opt/homebrew/bin/greadlink "$#"
Make it executable
chmod +x ~/scripts/readlink
Check which readlink is the first in PATH
type -a readlink
Output should be something like.
readlink is /home/zlZimon/scripts/readlink
readlink is /usr/bin/readlink
Note that the current work around is for a single user, or rather the user that has scripts directory in PATH, for a system wide approach one can use the path from homebrew or /usr/local/ or whichever default is available for all users.

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

bash - cd command not working?

I've somehow managed to screw up bash while fiddling with the $PATH variable in my bash_profile (I think...). All I did, as far as I can remember, was add a directory to the $PATH variable. Please HELP!
Here's what I get when I cd into various directories
my-MacBook-Pro:~ myuser$ cd .rvm
-bash: dirname: command not found
-bash: find: command not found
my-MacBook-Pro:.rvm myuser$ cd
-bash: find: command not found
And here's what happens when I try to get into my .bash_profile to undo whatever it is that I did...
my-MacBook-Pro:~ myuser$ emacs .bash_profile
-bash: emacs: command not found
my-MacBook-Pro:~ myuser$ sudo emacs .bash_profile
-bash: sudo: command not found
Any help would be massively appreciated. I'm completely screwed until I can get bash working normally again!
/usr/bin/emacs .bash_profile or similar should work when the PATH is broken.
The $PATH variable tells the shell where to look for commands. If you just bypass that by telling it the full path, it should work. Try /usr/bin/emacs .bash_profile.
When you do a cd, you're getting a bunch of other things. Since you're using BASH there are are two possible issues:
You have PROMPT_COMMAND defined. Try to undefining it:
$ unset PROMPT_COMMAND
There's an alias of the cd command: This was quite common in Kornshell where you don't have the nice backslashed characters you could put into your prompt string. If you wanted your prompt to have the name of your directory in it.
You had to do something like this:
function _cd
{
logname="$(logname)"
hostname="$(hostname)"
directory="$1"
pattern="$2"
if [ "$pattern" ] #This is a substitution!
then
\cd "$directory" "$pattern"
elif [ "$directory" ]
then
\cd "$directory"
else
\cd
fi
directory=$PWD
shortName=${directory#$HOME}
if [ "$shortName" = "" ]
then
prompt="~$logname"
elif [ "$shortName" = "$directory" ]
then
prompt="$directory"
else
prompt="~$shortName"
fi
title="$logname#$hostname:$prompt"
PS1="$title
$ "
}
alias cd="_cd"
Ugly isn't it? You don't have to go through all of that for BASH, but this does work in BASH too, and I've seen places where this was done either out of ignorance of inertia.
Try this:
$ type cd
You'll either get
$type cd
cd is a shell builtin
or you'll get
$ type cd
cd is an alias for ....
As for your updating of $PATH, you probably forgot to put $PATH back in the new definition, or quotation marks because someone has a directory name with a space in it. Your PATH setting should look like this:
PATH="/my/directory:$PATH"
Some people say it should be:
PATH="$PATH:/my/directory"
I guess, that you have defined $PROMPT_COMMAND (maybe in .bashrc) in a way that uses dirname and find.
That would explain the behavior of cd.
The find command is by default in /usr/bin/find. Thus, you can use it to find the locations of your imprtant commands and reconstruct you path information.

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