Search code examples
bashshellcommand-substitutioncoproc

indirect bash command execution not working as expected with coproc


I'm quite new to linux shell scripting and have a question:

1.) why does the command

test1="leafpad" && coproc exec "$test1"

work in bash (commandline, GNU bash 4.4.12 on a debian derivate linux), but the command

test1="coproc" && exec "$test1" leafpad

does not? Error messages: bash: exec: coproc: Not found.

whereas coproc leafpad does work as expected.

How this command must be correct quouted to make it work? I've tried already

test1=`coproc` && exec "$test1" leafpad

test1='coproc' && exec "$test1" leafpad

test1="'coproc'" && exec "$test1" leafpad

test1=`coproc` && exec '$test1' leafpad

test1=`coproc` && exec `$test1` leafpad

test1="coproc" && exec $test1 leafpad

test1=`coproc` && exec $test1 leafpad

and some more variations, but none of them works.

2.) This was the test on commandline only. But what I rather need is to do this within a script: So I'm sure there are to be done some additional quotations or masquerading of special characters.

Background: I have to execute a command, containing many arguments, some of them replaced by variables. Think of something like yad with all its possible arguments in a couple of lines, but let's create an easier example:

my_codeset="437"
my_line="20"
my_filename="somthing.txt"
if [ $value == 0 ]; then my_tabwidth='--tab-width=10'; else my_tabwidth=""; fi   # set tabs or not

leafpad --codeset="$my_codeset" "$my_tabwidth" --jump="$my_line" "$my_filename"

wherein the variables given are subject of change, as a function of user interaction before.

Now this complete command (which is about 6 lines of code in original), needs to be executed in two variants: one time headed by coproc, and another time not, as a function of an conditional branch.

so what I want is:

if [ $something == 1 ]; then copr_this="coproc"; else copr_this=""; fi

exec '$copr_this' <mycommand, with all its quoted arguments>

instead of

if [ something == 0]; then
          lengthy command here
else
          coproc lengthy command here, exactly repeated.
fi

I tried to manage it the other way around already, which was to put the complete lengthy command in a variable, and execute it in an conditional branch:

my_command=`lengthy command with some arguments $arg1 $arg2 ...`

if...
      exec "$my_command"
else
      coproc exec "$my_command"
fi

which stopped with error message "not found" also. Different ways of quoting didn't solve it, but produced different error messages only. I didn't manage to find out the correct quoting for this task. How shold this qouting read correctly?

For sure I could repeat the 6 lines of command in the code, but I'm quite sure this could be done more convenient.

As stated in the beginning: The indirect command execution works on commandline (and within script also), as long as coproc isn't involved. I cant't get it to work with coproc.

Any help and hints appreciated.


Update after first answer from @Socowi:

Thank you for your comprehensive and quick answer, Socowi. You are obviously right about coproc not beeing a command. So I understand now why my attempts had to fail. The exec command was added only during my experiments. I had started without this, but after having no success I thought it could help. It was an act of desperation merely. The backward quotes in the line my_command=`lengthy command with some arguments $arg1 $arg2 ...` were a typo, there should have been normal quotes, as you pointed out, since I intended to execute the command within the if of course. I'll probably head for the way you directed me to, using function {...} within script. But having experimented on this question in the meantime, I came to an astonishing solution: Astonishing for me, because of the difference between coproc not beeing a command and leafpad with its binary beeing a command. So it should be clear that test1='coproc' && test2='leafpad' && "$test1 $test2" will fail with error message bash: coproc leafpad: command not found., which is true. But now: why would test1='coproc' && test2='leafpad' && /bin/bash -c "$test1 $test2" do the job, starting leafpad, allowing to enter further commands in bash parallel, just as if I had entered leafpad & only? But this time executing both, the builtin (or keyword?) and the command, from a variable, which was refused when trying to enter it directly in the first bash instance. What is true for the first instance of bash should be true for the second also, or do I have a false perspective? Why does it work this way? Does the -c option do anything else than to execute the command?


Solution

  • Quoting is not the problem here. There are two other problems:

    Order of exec and coproc and builtins vs. binaries

    test1="leafpad" && coproc exec "$test1" is the same as coproc exec leafpad.
    test1="coproc" && exec "$test1" leafpad is the same as exec coproc leafpad.

    The order makes a difference: coproc exec vs. exec coproc. The latter does not work because exec replaces the current shell with the specified program. However, coproc is a builtin command. There is no coproc binary on your system. You can run it only from inside bash. Therfore exec fails.

    Command Substitution vs. Strings

    In your script ...

    my_command=`lengthy command`
    if ...; then
          exec "$my_command"
    else
          coproc exec "$my_command"
    fi
    

    ... you did not store lengthy command inside the variable, but you ran that command and stored its output (v=`cmd` is the same as v=$(cmd)) before the if. Then inside the if, you tried to execute the output of the command as another command.

    To store the command as a string and execute it later you could use my_command="lengthy command"; $my_command (note the intentionally missing quotes). However, bash offers far better ways to store commands. Instead of strings use arrays or functions. Here we use a function:

    my_command() {
      exec lengthy command
    }
    
    if ...; then
       coproc my_command
    else
       my_command
    fi
    

    coproc exec?

    That being said, I really wonder about the combination of coproc and exec. To me it seems coproc exec cmd ignores the exec part and is the same as coproc cmd. If exec acted normally here, the current shell would be replaced, you would loose the COPROC array and therefore wouldn't need the coproc. Either way, using both at the same time seems strange. Are you really sure you need that exec there? If so, I'd be happy to hear the reasons.