0

I can write the command

exec sleep '5'

and it works just fine.

But say I have a file called cmd.txt containing the following string

sleep '5'

I want to read this into a variable, then I want to exec this command.

cmd=$(cat cmd.txt)
exec $cmd

Then this fails with.

+ exec sleep ''\''5'\'''
sleep: invalid time interval ‘'5'’
Try 'sleep --help' for more information.

The sleep command is a trivial example to demonstrate the problem, the real command is a lengthy java command also with some single quotes in it. It needs to be exec and not eval as we need to replace the current process.

A workaround involves creating a file with "exec " followed by the command and running.

. file

But I would like to avoid creating a file just to do this.

The full command is, and is generated by some other code

/opt/java/jdk-17.0.6+10/bin/java -Djava.io.tmpdir=/tmp -Djetty.home=/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home -Djetty.base=/tmp/jetty-test --class-path /tmp/jetty-test/resources:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/logging/slf4j-api-2.0.5.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/logging/jetty-slf4j-impl-10.0.15-SNAPSHOT.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/jetty-servlet-api-4.0.6.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/jetty-http-10.0.15-SNAPSHOT.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/jetty-server-10.0.15-SNAPSHOT.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/jetty-xml-10.0.15-SNAPSHOT.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/jetty-util-10.0.15-SNAPSHOT.jar:/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/jetty-io-10.0.15-SNAPSHOT.jar org.eclipse.jetty.xml.XmlConfiguration 'java.version'='17.0.6' 'java.version.major'='17' 'java.version.micro'='6' 'java.version.minor'='0' 'java.version.platform'='17' 'jetty.base'='/tmp/jetty-test' 'jetty.base.uri'='file:///tmp/jetty-test' 'jetty.home'='/home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home' 'jetty.home.uri'='file:///home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home' 'jetty.http.port'='8181' 'jetty.webapp.addServerClasses'='org.eclipse.jetty.logging.,file:///home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/lib/logging/,org.slf4j.' 'runtime.feature.alpn'='true' 'slf4j.version'='2.0.5' /home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/etc/jetty-bytebufferpool.xml /home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/etc/jetty-threadpool.xml /home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/etc/jetty.xml /home/lachlan/webtide/jetty-10.0.x/jetty-home/target/jetty-home/etc/jetty-http.xml
tripleee
  • 175,061
  • 34
  • 275
  • 318
Lachlan
  • 356
  • 1
  • 7
  • Is this a pure theoretical question, because `sleep 5` without any quotes is working fine? – Dominique Apr 26 '23 at 06:40
  • Would you provide the actual (or simplified) java command? I suppose the noted single quotes will not be essential for the final command execution (as `'5'` for `sleep`) and there will exist some alternatives for them. – tshiono Apr 26 '23 at 07:23
  • Just `bash ./cmd.txt`, why the reading. Overall, you should not store commands in a variable. You should use a bash array. – KamilCuk Apr 26 '23 at 07:23
  • _Quote removal_ is not performed on quote characters that resulted from parameter expansions. The argument of `sleep` is literally `'5'` (not `5`) in the latter case. – M. Nejat Aydin Apr 26 '23 at 07:25
  • The cmd.txt may have some additional arguments appended by the script before the exec. – Lachlan Apr 26 '23 at 07:25
  • The actual command is now appended to the question. – Lachlan Apr 26 '23 at 07:26
  • Not sure about java but as you mentioned linux command here is trick which you can use `cmd=$(cat cmd.txt); echo $cmd | sh` – S. Mondal Apr 26 '23 at 07:37
  • The argument to `exec` should be a file name in your `PATH`, not a literal string. You might be looking for `eval`; but the general guidance is don't use `eval`, in particular if (as it seems) you don't know exactly what you are doing. – tripleee Apr 26 '23 at 07:42
  • None of the single-quotes in the actual command are actually doing anything, so you could just leave them off entirely. Single-quotes change how the characters inside them are parsed, but none of those characters are affected by the changes, so the quotes are irrelevant. – Gordon Davisson Apr 26 '23 at 08:06
  • _But I would like to avoid creating a file just to do this._ : Your argument is funny. In your first attempt, you saw no problem in creating a file `cmd.txt` just for this purpose, but you see now a problem in creating the file named `file`. – user1934428 Apr 26 '23 at 08:59
  • 1
    But basically I wonder **why** you want to use `exec` in the first place. This would replace the current shell with the command being executed, which means that your bash shell is completely gone. Could it be that you have here a [XY Problem](https://en.wikipedia.org/wiki/XY_problem)? – user1934428 Apr 26 '23 at 09:01
  • `exec cmd.txt` would do that, provided that `cmd.txt` is executable, and in your `PATH`. If you just want to evaluate its contents, maybe `source cmd.txt`. – tripleee Apr 26 '23 at 11:20
  • Does this answer your question? [How can I store a command in a variable in a shell script?](https://stackoverflow.com/questions/5615717/how-can-i-store-a-command-in-a-variable-in-a-shell-script) – pjh Apr 26 '23 at 12:52
  • I need to use exec and not eval as it needs to replace the current shell. This is a requirement because it needs to replace entrypoint in the docker image. I'm getting from this that there is no way to do it without using a file. – Lachlan Apr 26 '23 at 13:43
  • The command is generated by other code which includes the single quotes so I don't have the option to leave them off. Obviously it is unnecessary to have them in the sleep command but that was just an example. – Lachlan Apr 26 '23 at 13:45
  • `cmd.txt` sounds like it should start with `#!/bin/sh`, have its execute-bit set, so that you can simply write `exec ./cmd.txt`. No need to explicitly read the contents into a variable. – chepner Apr 27 '23 at 12:51

2 Answers2

1

I can write

Bash is not a macro replacement language. What you write != what bash expands, there are different rules for what is written and what is the result of expansion.

You should not be storing commands in a variable. Use a bash array. Read https://mywiki.wooledge.org/BashFAQ/050.

cmd=(sleep '5')
"${cmd[@]}"

If you really can't and you have a command that is only sometimes quoted, you can parse the quotes with xargs:

cmd='echo "argument with spaces     in quotes"'
# Just using env -- to make xargs execute the command, otherwise
# xargs executes echo.
xargs env -- <<<"$cmd"

If you really can't, and you have a command as-if it would be typed on the command line including any command substitutions, filename expansions and so on, then you have to spawn a subshell:

cmd="sleep '5' ; ls * ; echo \$(seq 10)"
bash -c "$cmd"

Note the quotes. Check your scripts with shellcheck !

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • this works when you type cmd=(sleep '5'), but if I try and read that in from a file like this read -a cmd < cmd.txt, then I get the same issue. – Lachlan Apr 26 '23 at 13:34
  • If you have a file _and the file is written in bash_, use the file `bash ./cmd.txt`. As said, what you type is not what is the result of expansion. I do not understand - you have to specify the _format_ or escaping rules that the file uses to store the command, then deserialize and then run. You question contains `But I would like to avoid creating a file` , but you state that there is a file - please clarify. – KamilCuk Apr 26 '23 at 14:05
  • There is a generated file which contains only a java command (which has single quotes in it) which I need to exec from a separate bash script to replace the current shell. I found that I could do this by creating a new file starting with exec followed by the contents of the generated file, however I would like to avoid creating a new file just to exec this. – Lachlan Apr 26 '23 at 14:34
  • @Lachlan : Are you sure that you want to **exec** it and not just **execute** it as a child process? – user1934428 Apr 27 '23 at 06:04
  • `There is a generated file` `with exec followed by the contents of the generated file` ` would like to avoid creating a new file` I do not understand, so do not create a file. If you have a command with only quotes and no shell expressions, use `xargs` as in my answer. Read documentation. – KamilCuk Apr 27 '23 at 06:08
-1

In exec $cmd , the shell does not resolve the quotes, because quote removal happens before parameter expansion. Therefore, the command sleep receives as argument the string '5', and this indeed is not a valid number.

You would get the same effect by

arg="'5'" 
sleep $arg

In sleep '5', the shell performs quote removal and sleep sees as argument the string 5.

user1934428
  • 19,864
  • 7
  • 42
  • 87