How to get parent folder of executing script in zsh? - bash

In bash i get the executing script's parent folder name by this line
SCRIPT_PARENT=`readlink -f ${BASH_SOURCE%/*}/..`
Is there any way to achieve this in zsh in a way that works both in zsh and bash?
Assume i have got a file /some/folder/rootfolder/subfolder/script with the contents:
echo `magic-i-am-looking-for`
I want it to behave this way:
$ cd /some/other/folder
$ . /some/folder/rootfolder/subfolder/script
/some/folder/rootfolder
$ . ../../folder/rootfolder/subfolder/script
/some/folder/rootfolder
$ cd /some/folder/rootfolder
$ . subfolder/script
/some/folder/rootfolder
$ cd subfolder
$ . script
/some/folder/rootfolder
This should work in bash and zsh. My first implements this behavior, but does due to $BASH_SOURCE not work in zsh.
So basically its:
Is there a way to emulate $BASH_SOURCE in zsh, that also works in bash?

I now realized that $0 in zsh behaves like $BASH_SOURCE in bash. So using $BASH_SOURCE when available and falling back to $0 solves my problem:
${BASH_SOURCE:-$0}
There is a little zsh edge case left, when sourcing from $PATH like:
zsh> cat ../script
echo \$0: $0
echo \$BASH_SOURCE: $BASH_SOURCE
echo '${BASH_SOURCE:-$0}:' ${BASH_SOURCE:-$0}
zsh> . script
$0: script
$BASH_SOURCE:
${BASH_SOURCE:-$0}: script
bash> . script
$0: bash
$BASH_SOURCE: /home/me/script
${BASH_SOURCE:-$0}: /home/me/script
I could do a which script but this would not play nice with other cases

While it would be easy to do this in zsh, it is just as easy to use pure bash which is able to be evaluated in zsh. If you cannot use any command that may or may not be on your path, then you can only use variable alteration to achieve what you want:
SCRIPT_SOURCE=${0%/*}
This is likely to be a relative path. If you really want the full path then you will have to resort to an external command (you could implement it yourself, but it would be a lot of work to avoid using a very available command):
SCRIPT_SOURCE=$(/bin/readlink -f ${0%/*})
This doesn't depend on your $PATH, it just depends on /bin/readlink being present. Which it almost certainly is.
Now, you wanted this to be a sourced file. This is fine, as you can just export any variable you set, however if you execute the above then $0 will be the location of the sourced file and not the location of the calling script.
This just means you need to set a variable to hold the $0 value which the sourced script knows about. For example:
The script you will source:
echo ${LOCATION%/*}
The script that sources that script:
LOCATION=$0
<source script here>
But given that the ${0%/*} expansion is so compact, you could just use that in place of the script.

Because you were able to run the command from your $PATH I'll do something like that:
SCRIPT_PARENT=$(readlink -f "$(which $0)/..")
Is that your desired output?

Related

With Bash or ZSH is there a way to use a wildcard to execute the same command for each script?

I have a directory with script files, say:
scripts/
foo.sh
script1.sh
test.sh
... etc
and would like to execute each script like:
$ ./scripts/foo.sh start
$ ./scripts/script1.sh start
etc
without needing to know all the script filenames.
Is there a way to append start to them and execute? I've tried tab-completion as it's pretty good in ZSH, using ./scripts/*[TAB] start with no luck, but I would imagine there's another way to do so, so it outputs:
$ ./scripts/foo.sh start ./scripts/script1.sh start
Or perhaps some other way to make it easier? I'd like to do so in the Terminal without an alias or function if possible, as these scripts are on a box I SSH to and shouldn't be modifying *._profile or .*rc files.
Use a simple loop:
for script in scripts/*.sh; do
"$script" start
done
There's just one caveat: if there are no such *.sh files, you will get an error. A simple workaround for that is to check if $script is actually a file (and executable):
for script in scripts/*.sh; do
[ -x "$script" ] && "$script" start
done
Note that this can be written on a single line, if that's what you're after for:
for script in scripts/*.sh; do [ -x "$script" ] && "$script" start; done
Zsh has some shorthand loops that bash doesn't:
for f (scripts/*.sh) "$f" start

Bash: Path to symlink which calls this script

I have the following situation:
I have a script with a path of: /usr/local/bin/rsnapshot.period
I want to have symlinks to it in various /etc/cron.[period]/ directories, like /etc/cron.hourly/rsnapshot
I'd like to have the script look up the full path to the symlink, and pull out the [period] part, so I can feed it to rsnapshot.
I can do all the text hacking. The problem I'm having trouble with is getting the path to the calling symlink from within the bash script. $0 seems to point to /usr/local/bin/rsnapshot.period
Is there a better way to get this info?
$0 seems to point to /usr/local/bin/rsnapshot.period
$0 is set by the calling program in its exec*() call, as the first word of the arg argument or the first element of the argv argument. If you feel that the tool you're using is setting this value incorrectly then you should open a bug with the developer.
In the meantime, using a hardlink instead of a symlink will allow you to detect the script name properly, but will break if you aren't careful with the tool you use to edit the main script.
Turns out my problem wasn't that $0 was incorrect - it was pointing to the right place. However, as I was trying to get the absolute path of it, I was using 'realpath' before it, which resolved symlinks.
Passing realpath the '-s' fixed it. Here's my test script and the output of it:
Script:
#!/bin/sh
echo \$0: $0
echo realpath -s \$0: $(realpath -s $0)
echo readlink -e: $(readlink -e $0)
Executed:
$0: ./rsnapshot
realpath -s $0: /etc/cron.hourly/rsnapshot
readlink -e: /usr/local/bin/rsnapshot.period

How to find script directory in an included shell script

We now to find the directory of a shell script using dirname and $0, but this doesn't work when the script is inluded in another script.
Suppose two files first.sh and second.sh:
/tmp/first.sh :
#!/bin/sh
. "/tmp/test/second.sh"
/tmp/test/second.sh :
#!/bin/sh
echo $0
by running first.sh the second script also prints first.sh. How the code in second.sh can find the directory of itself? (Searching for a solution that works on bash/csh/zsh)
There are no solution that will work equally good in all flavours of shells.
In bash you can use BASH_SOURCE:
$(dirname "$BASH_SOURCE")
Example:
$ cat /tmp/1.sh
. /tmp/sub/2.sh
$ cat /tmp/sub/2.sh
echo $BASH_SOURCE
$ bash /tmp/1.sh
/tmp/sub/2.sh
As you can see, the script prints the name of 2.sh,
although you start /tmp/1.sh, that includes 2.sh with the source command.
I must note, that this solution will work only in bash. In Bourne-shell (/bin/sh) it is impossible.
In csh/tcsh/zsh you can use $_ instead of BASH_SOURCE.

How do I get the script name being executed in bash?

So I am trying to make a portable bashrc/bash_profile file. I have a single script that I am symbolically linking to .bashrc, .bash_profile, etc. I am then looking at $0 and switching what I do based on which script was called. The problem is what the shell calls the bashrc script of course it executes bash really which means $0 for me is -bash. $1 further more is not set to the script name.
So my question is, in bash how can I get the name of the script being executed. Not the binary executing it, e.g. bash?
I assume its giving me -bash with $1 not being set because it is really not a new process. Any ideas?
Try:
readlink -f ${BASH_SOURCE[0]}
or just:
${BASH_SOURCE[0]}.
Remarks:
$0 only works when user executes "./script.sh"
$BASH_ARGV only works when user executes ". script.sh" or "source script.sh"
${BASH_SOURCE[0]} works on both cases.
readlink -f is useful when symbolic link is used.
The variable BASH_ARGV should work, it appears the script is being sourced
$BASH_ARGV
create .sh file lets say view.sh then put
#!/bin/bash
echo "The script is being executed..."
readlink -f ${BASH_SOURCE[0]}

Refer to the current directory in a shell script

How do I refer to the current directory in a shell script?
So I have this script which calls another script in the same directory:
#! /bin/sh
#Call the other script
./foo.sh
# do something ...
For this I got ./foo.sh: No such file or directory
So I changed it to:
#! /bin/sh
#Call the other script
foo.sh
# do something ...
But this would call the foo script which is, by default, in the PATH. This is not what I want.
So the question is, what's the syntax to refer ./ in a shell script?
If both the scripts are in the same directory and you got the ./foo.sh: No such file or directory error then the most likely cause is that you ran the first script from a different directory than the one where they are located in. Put the following in your first script so that the call to foo.sh works irrespective of where you call the first script from:
my_dir=`dirname $0`
#Call the other script
$my_dir/foo.sh
The following code works well with spaces and doesn't require bash to work:
#!/bin/sh
SCRIPTDIR="$(dirname "$0")"
#Call the other script
"$SCRIPTDIR/foo.sh"
Also if you want to use the absolute path, you could do this:
SCRIPTDIR=`cd "$(dirname "$0")" && pwd`
This might help you:
Unix shell script find out which directory the script file resides?
But as sarnold stated, "./" is for the current working directory.
In order to make it POSIX:
a="/$0"; a=${a%/*}; a=${a:-.}; a=${a#/}/; BASEDIR=$(cd $a; pwd)
Tested on many Bourne-compatible shells including the BSD ones.
As far as I know I am the author and I put it into public domain. For more info see:
https://blog.jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
script_dir="${BASH_SOURCE%/*}" # rm the last / and the file name from BASH_SOURCE
$script_dir/foo.sh
Reference: Alex Che's comment above.
The accepted solution does not work if you have a space in the path to the directory containing the scripts.
If you can use bash, this worked for me:
#!/bin/bash
SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
"${SCRIPTDIR}/foo.sh"

Resources