1

I'm writing a shell script to run on a docker image based on Alpine. It's shell is /bin/sh.

What I'm trying to do is execute a function for the results of a find command. The following works in my local bash and sh shells.

myscript.sh:

#!/bin/sh

function get_tags {
  # do stuff
}


export -f get_tags

# get all YAML files in ./assets/config that have 'FIND' somewhere in the filename
# pass each to the get_tags function
find ./assets/config -type f \( -iname "Find*.yaml" -or -iname "Find*.yml" \) -exec sh -c 'get_tags "$0"' {} \;

When I run it on the alpine image, however, I get the following error:

./myscript.sh: export: line 31: illegal option -f

Is there another way I can do this?

My question is NOT "what is the difference between sh and bash". My question is: how do I accomplish the task of running a function on the output of the find command.

ebbishop
  • 1,833
  • 2
  • 20
  • 45
  • Use `#!/bin/bash`, not `#!/bin/sh` – hek2mgl May 10 '19 at 16:15
  • 1
    Also, needs to be `-exec bash -c` not `-exec sh -c` if you want exported functions to work. – Charles Duffy May 10 '19 at 16:16
  • 1
    @ebbishop, ...sounds like your *local* `sh` is actually a copy of bash (which disables some but not all extensions when called under the `sh` name) -- but that's not guaranteed; the only functionality you can assume in `sh` is that narrowly documented in the POSIX sh specification. – Charles Duffy May 10 '19 at 16:16
  • BTW, the legacy ksh syntax (which bash supports for compatibility) `function funcname {` isn't guaranteed to work in `sh` either; the only POSIX-defined function declaration syntax is `funcname() {` with no `function` preceding. See also http://wiki.bash-hackers.org/scripting/obsolete – Charles Duffy May 10 '19 at 16:17
  • Using `#!/bin/bash` results in the error `/bin/sh: eval: line 124: ./myscript.sh: not found`. I get that there is different syntax for `sh` - my question is - what is it? Am I trying to do something impossible? Is there perhaps a workaround? – ebbishop May 10 '19 at 16:21
  • @hek2mgl If my question is actually answered in the linked duplicate, can you point me to the place where it's answered? I'm not seeing any reference to exporting functions and/or how to use them with `-exec` command of `find` – ebbishop May 10 '19 at 16:28
  • 1
    Ok, I reopened it. Sorry if I was wrong here – hek2mgl May 10 '19 at 16:36
  • 2
    Sounds like you don't have bash installed. If you want bash-only features, *install bash*. See [How to use bash in an Alpine-based docker image?](https://stackoverflow.com/questions/40944479/how-to-use-bash-with-an-alpine-based-docker-image) – Charles Duffy May 10 '19 at 16:45
  • 1
    PS: Please also research the topic "Why Alpine linux s**ks for containers" on Google ;) – hek2mgl May 10 '19 at 16:52
  • 1
    Why would you want to do this? Exporting a function is non-robust, unsafe, unreliable, and just plain icky. Either put the function in a script in a regular file and call it, or define the function in the string you pass to sh, or source it in the exec'd shell (not sure if overhead will be worse here than reading from the environ, but using `+` instead of `;` will certainly help if it's an issue), or...anything else. Don't abuse the environment. – William Pursell May 10 '19 at 18:20
  • That's worth an answer imo ^^^ – hek2mgl May 10 '19 at 19:05

2 Answers2

1

You need to use bash, like this:

#!/bin/bash
fun() { echo "fun ${1}" ; }
export -f fun
find . -name 'foo' -exec bash -c 'fun "${1}"' -- {} \;

The key here is to run bash -c 'fun "${1}"' -- {} \;. You can't call the function directly (and pass arguments to it). You need to wrap it into a minimal script where this minimal script receives the argument passed by find and passes it through to the function.


Note: I'm passing two arguments to bash -c: the string -- and the actual filename {}. I'm doing this by convention, because argument counting starts at $0 when a script is executed by bash -c, in opposite to $1 when running a script the normal way (in a file, not via bash -c)

bash -c 'fun "${0}"' {} \; would work, but people might think $0 is the script name like they know it from normal scripts.

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
1

Exporting functions is a Bash feature. Alpine Linux does not come with Bash.

You can instead use a while read loop to process the results, as this is POSIX and will work on all shells:

get_tags() {
  echo "Getting tags for $1"
}

find ./assets/config -type f \( -iname "Find*.yaml" -o -iname "Find*.yml" \) |
    while IFS="" read -r file
    do
      get_tags "$file"
    done
that other guy
  • 116,971
  • 11
  • 170
  • 194