0

I have a test.sh file which takes as a parameter a bash command, it does some logic, i.e. setting and checking some env vars, and then executes that input command.

#!/bin/bash

#Some other logic here

echo "Run command: $@"
eval "$@"

When I run it, here's the output

% ./test.sh echo "ok"        
Run command: echo ok
ok

But the issue is, when I pass something like sh -c 'echo "ok"', I don't get the output.

% ./test.sh sh -c 'echo "ok"'
Run command: sh -c echo "ok"

%

So I tried changing eval with exec, tried to execute $@ directly (without eval or exec), even tried to execute it and save the output to a variable, still no use.

Is there any way to run the passed command in this format and get the ourput?


Use case: The script is used as an entrypoint for the docker container, it receives the parameters from docker CMD and executes those to run the container.

As a quickfix I can remove the sh -c and pass the command without it, but I want to make the script reusable and not to change the commands.

Henry Harutyunyan
  • 2,355
  • 1
  • 16
  • 22

1 Answers1

2

TL;DR:

This is a typical use case (perform some business logic in a Docker entrypoint script before running a compound command, given at command line) and the recommended last line of the script is:

exec "$@"

Details

To further explain this line, some remarks and hyperlinks:

  1. As per the Bash user manual, exec is a POSIX shell builtin that replaces the shell [with the command supplied] without creating a new process.

  2. As a result, using exec like this in a Docker entrypoint context is important because it ensures that the CMD program that is executed will still have PID 1 and can directly handle signals, including that of docker stop (see also that other SO answer: Speed up docker-compose shutdown).

  3. The double quotes ("$@") are also important to avoid word splitting (namely, ensure that each positional argument is passed as is, even if it contains spaces). See e.g.:

     #!/usr/bin/env bash
    
     printargs () { for arg; do echo "$arg"; done; }
    
     test0 () {
         echo "test0:"
         printargs $@
     }
    
     test1 () {
         echo "test1:"
         printargs "$@"
     }
    
     test0 /bin/sh -c 'echo "ok"'
     echo
     test1 /bin/sh -c 'echo "ok"'
    
    test0:
    /bin/sh
    -c
    echo
    "ok"
    
    test1:
    /bin/sh
    -c
    echo "ok"
    
  4. Finally eval is a powerful bash builtin that is (1) unneeded for your use case, (2) and actually not advised to use in general, in particular for security reasons. E.g., if the string argument of eval relies on some user-provided input… For details on this issue, see e.g. https://mywiki.wooledge.org/BashFAQ/048 (which recaps the few situations where one would like to use this builtin, typically, the command eval "$(ssh-agent -s)").

ErikMD
  • 13,377
  • 3
  • 35
  • 71