Differences between revisions 3 and 9 (spanning 6 versions)
Revision 3 as of 2008-12-29 14:00:44
Size: 1704
Editor: Lhunath
Comment: Clarify FAQ#12
Revision 9 as of 2014-04-22 01:18:18
Size: 3339
Editor: GreyCat
Comment: Calling shell functions
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
== How do I invoke a bash command from a non-shell application? == == How do I invoke a shell command from a non-shell application? ==
Line 4: Line 4:
You can use `bash`'s `-c` option to run the bash process with the sole purpose of executing a short bit of script: You can use the shell's `-c` option to run the shell with the sole purpose of executing a short bit of script:
Line 6: Line 6:
    bash -c 'echo "Hi! This is a short script."' sh -c 'echo "Hi! This is a short script."'
Line 9: Line 9:
This is usually pretty useless without a means if passing data to it. The best way to pass bits of data to your bash environment is to pass it as positional arguments. When invoking bash this way, we can set the positional parameters like this: This is usually pretty useless without a means of passing data to it. The best way to pass bits of data to your shell is to pass them as positional arguments:
Line 11: Line 11:
    bash -c 'echo "Hi! This short script was ran with the arguments: $@"' -- "foo" "bar" sh -c 'echo "Hi! This short script was run with the arguments: $@"' -- "foo" "bar"
Line 14: Line 14:
Notice the -- before the actual positional parameters. The first argument you pass to the bash process (that isn't the argument to the `-c` option) will be placed in `$0`! Positional parameters start at `$1`, so we put a little placeholder in `$0`. This can be anything you like; in the example, we use the generic `--`. Notice the -- before the actual positional parameters. The first argument you pass to the shell process (that isn't the argument to the `-c` option) will be placed in `$0`. Positional parameters start at `$1`, so we put a little placeholder in `$0`. This can be anything you like; in the example, we use the generic `--`.
Line 16: Line 16:
Alternatively, if your non-shell application allows you to set environment variables; you can do this, and then read them using normal bash variables of the same name as the environment variables.

This technique is used often in shell scripting, when trying to have a non-shell CLI utility execute some bash code, such as with `find(1)`:
This technique is used often in shell scripting, when trying to have a non-shell CLI utility execute some bash code, such as with [[UsingFind|find(1)]]:
Line 20: Line 18:
    find /foo -name '*.bar' -exec bash -c 'mv "$1" "${1%.bar}.jpg"' -- {} \; find /foo -name '*.bar' -exec bash -c 'mv "$1" "${1%.bar}.jpg"' -- {} \;
Line 23: Line 21:
Here, we ask `find(1)` to run the `bash` command for every `*.bar` file it finds, passing it to the `bash` process as the first positional parameter. The `bash` process runs the `mv` command after doing some `Parameter Expansion` on the first positional parameter in order to rename our file's extension from `bar` to `jpg`. Here, we ask `find` to run the `bash` command for every `*.bar` file it finds, passing it to the `bash` process as the first positional parameter. The `bash` process runs the `mv` command after doing some [[BashFAQ/073|Parameter Expansion]] on the first positional parameter in order to rename our file's extension from `bar` to `jpg`.

Alternatively, if your non-shell application allows you to set environment variables, you can do that, and then read them using normal variables of the same name.

Similarly, suppose a program (e.g. a file manager) lets you define an external command that an argument will be appended to, but you need that argument somewhere in the middle. In that case:
{{{#!format none
#!/bin/sh
sh -c 'command foo "$1" bar' -- "$@"
}}}

=== Calling shell functions ===

Only a shell can call a shell function. So constructs like this won't work:

{{{
# This won't work!
find . -type f -exec my_bash_function {} +
}}}

If your shell function is '''defined''' in a file, you can invoke a shell which sources that file, and then calls the function:

{{{
find . -type f -exec bash -c 'source /my/bash/function; my_bash_function "$@"' _ {} +
}}}

(See UsingFind for explanations.)

Bash also permits function definitions to be ''exported'' through the environment. So, if your function is defined within your current shell, you can export it to make it available to the new shell which `find` invokes:

{{{
export -f my_bash_function
find . -type f -exec bash -c 'my_bash_function "$@"' _ {} +
}}}

Alas, this technique does not work when trying to call the shell function on a remote system (e.g. over ssh). Sourcing a file containing the function definition on the remote system will work, if such a file is available. If no such file is available, the only viable approach is to ask the ''current'' shell to spit out the function definition, and feed that to the remote shell over the ssh channel:

{{{
{
    declare -f my_bash_function
    echo "my_bash_function foo 'bar bar'"
} | ssh -T user@host bash
}}}

Care must be taken when writing a script to send through ssh. [[BashFAQ/096|Ssh works like eval]], with the same concerns; see that FAQ for details.

----
CategoryShell

How do I invoke a shell command from a non-shell application?

You can use the shell's -c option to run the shell with the sole purpose of executing a short bit of script:

sh -c 'echo "Hi!  This is a short script."'

This is usually pretty useless without a means of passing data to it. The best way to pass bits of data to your shell is to pass them as positional arguments:

sh -c 'echo "Hi! This short script was run with the arguments: $@"' -- "foo" "bar"

Notice the -- before the actual positional parameters. The first argument you pass to the shell process (that isn't the argument to the -c option) will be placed in $0. Positional parameters start at $1, so we put a little placeholder in $0. This can be anything you like; in the example, we use the generic --.

This technique is used often in shell scripting, when trying to have a non-shell CLI utility execute some bash code, such as with find(1):

find /foo -name '*.bar' -exec bash -c 'mv "$1" "${1%.bar}.jpg"' -- {} \;

Here, we ask find to run the bash command for every *.bar file it finds, passing it to the bash process as the first positional parameter. The bash process runs the mv command after doing some Parameter Expansion on the first positional parameter in order to rename our file's extension from bar to jpg.

Alternatively, if your non-shell application allows you to set environment variables, you can do that, and then read them using normal variables of the same name.

Similarly, suppose a program (e.g. a file manager) lets you define an external command that an argument will be appended to, but you need that argument somewhere in the middle. In that case:

#!/bin/sh
sh -c 'command foo "$1" bar' -- "$@"

Calling shell functions

Only a shell can call a shell function. So constructs like this won't work:

# This won't work!
find . -type f -exec my_bash_function {} +

If your shell function is defined in a file, you can invoke a shell which sources that file, and then calls the function:

find . -type f -exec bash -c 'source /my/bash/function; my_bash_function "$@"' _ {} +

(See UsingFind for explanations.)

Bash also permits function definitions to be exported through the environment. So, if your function is defined within your current shell, you can export it to make it available to the new shell which find invokes:

export -f my_bash_function
find . -type f -exec bash -c 'my_bash_function "$@"' _ {} +

Alas, this technique does not work when trying to call the shell function on a remote system (e.g. over ssh). Sourcing a file containing the function definition on the remote system will work, if such a file is available. If no such file is available, the only viable approach is to ask the current shell to spit out the function definition, and feed that to the remote shell over the ssh channel:

{
    declare -f my_bash_function
    echo "my_bash_function foo 'bar bar'"
} | ssh -T user@host bash

Care must be taken when writing a script to send through ssh. Ssh works like eval, with the same concerns; see that FAQ for details.


CategoryShell

BashFAQ/012 (last edited 2019-06-06 17:29:00 by GreyCat)