0

I'd like to give feedback to the user of a bash script along the lines of "You've run the following command <exact command the user entered>, better to run it like this <better command>". For usability reasons I think it would be best to print the command entered by the user as precisely as possible.

One of the things one could do is this:

echo "You've run the following command:" ${0} "${@}"

but this wouldn't preserve quotations or escaped text. One could check "${@}" and see which entries require escaping or quotation, but the script couldn't tell which one of the two the user used, and reproduce it.

history doesn't have access to the last command in the shell that ran the script (also it's not by default enabled in scripts, for good reasons).

Question: What's the best way to reproduce the command entered by a user to run a script, as precisely as possible?

2 Answers 2

3

You can't get at the unparsed command line; the shell receives the command line and parses it before starting your code. (Assuming there is a shell, of course. There doesn't necessarily need to be one, though.)

Example. Save this as args, and make it executable (chmod a+x args):

#!/bin/sh
echo "0=$0, #=$#, *=($*)"               # Program, number of args, list of args
printf '> %s\n' "$@"                    # Each arg in turn

You can invoke it in any of these ways and the resulting output will be the same

./args 'one two' three
a='one two'; ./args "$a" three          # Notice $a is quoted so remains one arg
./args 'one'" two" three                # Quotes are stripped by the shell
a=one b=; ./args "$a two" $b three      # I really dislike unquoted variables
a=one; ./args "$a two" three            # Variations on a quoted theme
./args one\ two three                   # And more

The best you can hope for is to use "$@" and quote arguments as needed. If you have the non-standard getopt (not getopts) from the util‐linux credited to Frodo Looijaard you can use a couple of lines like this to get a quoted string:

line=$(getopt --shell sh '' -- "$@")    # Guess the command line
printf 'Command: %s %s\n' "$0" "${line# -- }"

But it's still not anywhere near a perfect reproduction of the original command line.

1
  • Thanks. "Can't do" isn't the answer I was hoping for but it's an answer. And I didn't know about "$@". Commented Dec 1, 2023 at 21:09
2

There is no firm way. Only the parent shell knows the exact quoting used; but the parent does not have to be a shell in the first place, it can be a process that gives your script an array of arguments without parsing a representation that requires quoting or escaping. If it's a shell, there is no standard interface that would allow your script to get information about quoting.

"The command entered by the user" may be just shell_function or ./the-script "${array[@]}", so it's not just about quoting if you want to replicate it "as precisely as possible".

If the parent shell is sophisticated enough, it may be possible to configure it beforehand, so your script can get information about the original form of the command. This would be cumbersome and shell-dependent (for a start see echo-literally here). If I was a user of your script, I wouldn't want to configure my interactive shell only to allow your script look smart.

A program in Windows can know its command string with quotes (see this other answer where it says "the situation is somewhat more complicated"), but I don't think a Bash script can use this "feature" even when started in Windows.

The best you can do is what you described:

One could check "${@}" and see which entries require escaping or quotation, but the script couldn't tell which one of the two the user used, and reproduce it.

In practice you can easily add quotes by using "${0@Q}" and "${@@Q}". Example:

foo=bar' 'baz
bash -c '
echo "You have run the following command: ${0@Q} ${@@Q}"
' "arbitrary name" arg1 arg\ 2 "arg'3"          'arg 4' "a r"\ g'\5' "$foo"

Output:

You have run the following command: 'arbitrary name' 'arg1' 'arg 2' 'arg'\''3' 'arg 4' 'a r g\5' 'bar baz'

The shell code passed after -c not only cannot tell the exact quoting used, it also cannot be aware foo was ever there.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .