Export a function from zsh to bash script - bash

I have a function defined in my .zshenv like this:
function my_func() {
echo $1
}
I then execute a bash script from the zsh which its content is:
type my_func
I got an error: /tmp/test.bash: line 3: type: my_func: not found
While if I type type my_func from the zsh I got: my_func is a shell function from
Is there a way to use zsh defined function in a bash script? It seems to work for the exported variables

How bash does it
Bash itself can export its functions to other bash shells. It does so by exporting a string environmental variable of the form:
BASH_FUNC_functionNameHere%%=() { functionBodyHere; }
So in theory you could use the following zsh-command to export your function for bash:
export "BASH_FUNC_my_func%%=() { $(echo; typeset -f my_func | tail -n+2) }"
However, this does not work because zsh doesn't allow %% in identifiers.
Workaround
Depending on how you start bash, you might be able to inject the function definition into the bash process.
When running a bash script like bash myScript or ./myScript you can use bash -c "$(typeset -f my_func); export -f my_func; myScript" instead.
When starting an interactive bash shell using bash, you can use bash -c "$(typeset -f my_func); export -f my_func; exec bash" instead.
The right way
Either way, your function has to be a polyglot. That is, the source code of the function has to be understood by both zsh and bash. Above approach is not really viable if you want to export many functions or want to call many bash scripts.
It would be easier to define each of your functions inside its own script file and add the locations of those scripts to $PATH. That way you can call your "functions" from every shell and they will always work independently from your current shell.
Replacing the functions by script files only works if your functions don't want to modify the parent shell. cd or setting variables has no effect on the caller. If you want to do stuff like this, you can still use a script file, but then have to source it using . myFunctionFile. For sourcing, the source code has to be a polyglot again.

Related

After alias, script didn't get variable [duplicate]

This question already has answers here:
How to use aliases defined in .bashrc in other scripts?
(6 answers)
Closed 2 years ago.
I'm using a Mac and I have this alias defined in .bashrc:
$cat .bashrc | grep la
alias la='ls -la'
then I try to use it in a script:
$cat ./mytest.sh
#!/bin/bash
la
It runs and says it cannot find la:
./mytest.sh: line 2: la: command not found
Why is this? I tried on both Mac and Linux, same error!
Your .bashrc is only used by interactive shells. https://www.gnu.org/software/bash/manual/bashref.html#Bash-Startup-Files says:
Invoked non-interactively
When Bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute. Bash behaves as if the following command were executed:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the filename.
As noted above, if a non-interactive shell is invoked with the --login option, Bash attempts to read and execute commands from the login shell startup files.
As you can see, there's nothing about .bashrc there. Your alias simply doesn't exist in the script.
But even if .bashrc were read, there's another problem:
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt.
So if you wanted aliases to work in a script, you'd have to do shopt -s expand_aliases first. Or just use a shell function instead of an alias:
la() {
ls -la
}
The simplest answer is to fix this issue is to do the 2 important things in your script -or it wont' work, if you just do one thing.
#!/bin/bash -i
# Expand aliases defined in the shell ~/.bashrc
shopt -s expand_aliases
After this, your aliases that you have defined in ~/.bashrc they will be available in your shell script (giga.sh or any.sh) and to any function or child shell within such script.
If you don't do that, you'll get an error:
your_cool_alias: command not found
At the beginning of the ~/.bashrc file usually can be found two lines as:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
This line aborts the inclusion for scripts which is not recommended anyway. For portability issues, you usually write the full command or define the alias in the script itself.

Why bash alias doesn't work in scripts? [duplicate]

This question already has answers here:
How to use aliases defined in .bashrc in other scripts?
(6 answers)
Closed 2 years ago.
I'm using a Mac and I have this alias defined in .bashrc:
$cat .bashrc | grep la
alias la='ls -la'
then I try to use it in a script:
$cat ./mytest.sh
#!/bin/bash
la
It runs and says it cannot find la:
./mytest.sh: line 2: la: command not found
Why is this? I tried on both Mac and Linux, same error!
Your .bashrc is only used by interactive shells. https://www.gnu.org/software/bash/manual/bashref.html#Bash-Startup-Files says:
Invoked non-interactively
When Bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute. Bash behaves as if the following command were executed:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the filename.
As noted above, if a non-interactive shell is invoked with the --login option, Bash attempts to read and execute commands from the login shell startup files.
As you can see, there's nothing about .bashrc there. Your alias simply doesn't exist in the script.
But even if .bashrc were read, there's another problem:
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt.
So if you wanted aliases to work in a script, you'd have to do shopt -s expand_aliases first. Or just use a shell function instead of an alias:
la() {
ls -la
}
The simplest answer is to fix this issue is to do the 2 important things in your script -or it wont' work, if you just do one thing.
#!/bin/bash -i
# Expand aliases defined in the shell ~/.bashrc
shopt -s expand_aliases
After this, your aliases that you have defined in ~/.bashrc they will be available in your shell script (giga.sh or any.sh) and to any function or child shell within such script.
If you don't do that, you'll get an error:
your_cool_alias: command not found
At the beginning of the ~/.bashrc file usually can be found two lines as:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
This line aborts the inclusion for scripts which is not recommended anyway. For portability issues, you usually write the full command or define the alias in the script itself.

Is function definition always exported in bash?

I an learning bash.
In bash manual, I found document about function that says
Functions may be exported so that subshells automatically
have them defined with the -f option to the export builtin.
In my bash, it exports definition of function contrary to the shell variable. But bash manual uses word, "may be exported". Is function definition always exported in bash or should I do something to guarantee its exportation?
Functions may be exported ... with the -f option to the export builtin.
That removes all the fluff. It should be clearer as to what it's trying to say.
And just in case it still isn't...
export -f <funcname>
Whether something (a variable or function) is exported or not determines whether it'll be passed on to subprocesses. For shell functions, this only really matters if the subprocess happens to be another shell. Here's an illustration:
$ exportedfunc() { echo "This is the exported function"; }
$ export -f exportedfunc
$ nonexportedfunc() { echo "This is the non-exported function"; }
$ bash # create a subshell to see which functions it inherits
$ PS1='\$\$ ' # set a different prompt so we can tell the subshell ($$) from the parent shell ($)
$$ exportedfunc # This'll work, because the parent shell exported the function
This is the exported function
$$ nonexportedfunc # This won't work because this function was not exported to subprocesses
bash: nonexportedfunc: command not found
$$ exit # back to the parent shell, where both functions are defined
$ exportedfunc
This is the exported function
$ nonexportedfunc
This is the non-exported function
I don't know of any shell setting that would cause all functions to be exported automatically. Although if you create a subshell implicitly (e.g. by putting some commands in parentheses), it'll inherit everything whether exported or not.

How do create an alias in shell scripts? [duplicate]

This question already has answers here:
using alias in shell script? [duplicate]
(7 answers)
Closed 2 years ago.
Definin an alias on Linux system is very simple.
From the following example we see that: the I_am_only_ls_alias alias command gives us the output as ls command
# alias I_am_only_ls_alias=ls
# I_am_only_ls_alias
Output:
file file1
But when I trying to do the same in bash script (define alias I_am_only_ls_alias), I get I_am_only_ls_alias: command not found.
Example of my bash script:
alias_test.bash
#!/bin/bash
alias I_am_only_ls_alias=ls
I_am_only_ls_alias
Run the bash script - alias_test.bash
/tmp/alias_test.bash
Output:
/tmp/: line 88: I_am_only_ls_alias: command not found
So, first I want to ask:
Why doesn't bash recognize the command I_am_only_ls_alias as an alias?
And what do I need to do in order to define aliases inside a bash script? Is it possible?
From the bash man page:
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt (see the
description of shopt under SHELL BUILTIN COMMANDS below).
So this should work:
#!/bin/bash
shopt -s expand_aliases
alias I_am_only_ls_alias=ls
I_am_only_ls_alias
Scripts usually use functions, not aliases.
Barmar's answer is the correct one for including an alias, but it's likely that you'll actually find a Bash function to be more flexible/useful.
For example this is materially the same as the alias version, but can be expanded much more easily:
timp#helez:~/tmp$ cat test.sh
#!/usr/bin/bash
function i_am_only_an_ls_func {
ls "$#"
}
i_am_only_an_ls_func
timp#helez:~/tmp$ ./test.sh
0600871h.html
[snip]
timp#helez:~/tmp$
The $# is irrelavent in this example, but it means that anything after i_am_only_an_ls_func will be added after the ls command, since $#, $1, $2, etc contain the arguments to the function, much the same as for a normal script. (Note that $0 is still the name of the parent script not the function)
Aliases cannot be defined in shell script that you execute - their effect will be gone once shell process finished execution.
You can, however, define aliases in your ~/.bashrc or in separate shell script that you source from (but not execute!). In that case, aliases are imported into already running shell process, and thus survive and actually work as you would expect.

Unable to use cd within a bash script as intended

Consider this script I wrote, which should go into parent directory, when no argument is given (the if ... part).
#/bin/bash
if (($# == 0))
then
cd ..
else
for basename
do
cd ${PWD%$basename*}$basename
done
fi
The problem is, that if I execute it like this
./up.sh
the cd is executed in a subshell, rendering it useless.
If I execute the script using source, it works, but I don't want to call it that way (I makes calling the script to complicated, also you would expect to call it directly if found in the PATH).
An arbitrary program (such as your bash program) cannot change the working directory of the parent process, as that would pretty much break all existing processes that spawn children.
You should define a bash alias or function instead. As you have discovered, typing source ./up.sh (or shorter: . ./up.sh) works too.
I suggest using a function instead of a script
function myscript()
{
// use $1, $2, "$#" as usual in scripts
local v1="bla" # can use globals
export PATH="$PATH" # but global shell env too
somedirectory=..
cd $somedirectory
}
Alternatively, alias would work (but it doesn't support redirection, argument passing, flow control etc well, and you cannot nest them in $() IIRC).
Lastly source the existing script in the current shell like so:
source ./script.sh
ksh and bash have shorthands for that:
. ./script.sh
Beware of scripts with 'exit' statements though: they will exit the parent shell!

Resources