2

I have a script where I dinamically change the arguments which must be passed to a command (mkvpropedit in this case). Consider the example script below:

#!/bin/bash

LANG_NAME="eng lish"

MYSETTINGS=()
MYSETTINGS+=("--edit 1")
MYSETTINGS+=("--set \"language=${LANG_NAME}\"")

echo "Count = ${#MYSETTINGS[@]}" # should be 2
set -x # enable to see the invoked command
mkvpropedit ${MYSETTINGS[@]}

When I run this, I get in the console:

[~] # ./test.sh
Count = 2
+ mkvpropedit --edit 1 --set '"language=eng' 'lish"'

But I would like not having the single quotes on the final invocation of mkvpropedit, like so:

+ mkvpropedit --edit 1 --set "language=eng lish"

I tried also echoing the array into a variable, and echo removes the single quote, but then I'm not able to use the variable as an argument of mkvpropedit because the single quotes appear again...

Of course the script has to work also if the variable is a single word, such as LANG_NAME="eng" . My Bash version is 3.2 (Busybox, actually).

Updated question

Probably the example below better explains what I'm trying to do. I've changed some names to be replicable.

#!/bin/bash

TITLE_NAME="my title"

MYSETTINGS=() MYSETTINGS+=("--edit track:2") MYSETTINGS+=("--set "name=${TITLE_NAME}"")

set -x mkvpropedit file.mkv ${MYSETTINGS[@]}

If I run this script, I get (due to the wrong quote):

# ./test.sh
+ mkvpropedit file.mkv --edit track:2 --set '"name=my' 'title"'
Error: More than one file name has been given ('file.mkv' and 'title"').

While if I run, manually:

# mkvpropedit file.mkv --edit track:2 --set "name=my title"
The file is being analyzed.
The changes are written to the file.
Done.

So it's definitely a quoting issue; I would like to invoke mkvpropedit using the array in the script.

Using eval

What seems to work, at the moment, is inserting mkvpropedit and file.mkv into the array and eventually call eval "${MYSETTINGS[@]}", but is it worth and safe? Isn't eval evil (pun intended)?

TITLE_NAME="my title"

MYSETTINGS=(mkvpropedit file.mkv) MYSETTINGS+=("--edit track:2") MYSETTINGS+=("--set "name=${TITLE_NAME}"")

set -x eval "${MYSETTINGS[@]}"

Returns:

# ./test.sh
+ eval mkvpropedit file.mkv '--edit track:2' '--set "name=my title"'
++ mkvpropedit file.mkv --edit track:2 --set 'name=my title'
The file is being analyzed.
The changes are written to the file.
Done.
virtualdj
  • 177

2 Answers2

5

There are no single quotes - that's just the shell's unambiguous representation of the variable's contents when you use set -x. You can see that if you instead look at the array elements using declare -p or by printing them one at a time:

LANG_NAME="eng lish"

MYSETTINGS=() MYSETTINGS+=("--edit 1") MYSETTINGS+=("--set "language=${LANG_NAME}"")

then

$ declare -p MYSETTINGS
declare -a MYSETTINGS=([0]="--edit 1" [1]="--set \"language=eng lish\"")

or

$ printf '>>>%s<<<\n' "${MYSETTINGS[@]}"
>>>--edit 1<<<
>>>--set "language=eng lish"<<<

However, you almost certainly want to pass --edit, 1, --set, and language=eng lish as separate tokens to the command, which means

  1. quoting each token that contains whitespace or glob characters during array construction, like language="${LANG_NAME}" or "language=${LANG_NAME}"

  2. double quoting the array expansion when you use it (to prevent word-splitting and filename generation - aka "split+glob")

So

LANG_NAME="eng lish"

MYSETTINGS=() MYSETTINGS+=(--edit 1) MYSETTINGS+=(--set language="${LANG_NAME}")

then

mkvpropedit file.mkv "${MYSETTINGS[@]}"

Note that you do not need additional double quotes around the variable expansion, because double-quoted "${name[@]}" expands each element of name to a separate word without further tokenization - further quotes like \"name=${TITLE_NAME}\" would be passed to the command literally.

See also How can we run a command stored in a variable?

steeldriver
  • 81,074
  • Mm, that's not clear. If I add the double quotes to the final statement, i.e. mkvpropedit "${MYSETTINGS[@]}" what I get with set -x is + mkvpropedit '--edit 1' '--set "language=eng lish"'. I know it's just a representation, but the thing is I need to pass the --set "language=eng lish" text as an argument to mkvpropedit and there the single quotes are added, because mkvpropedit complains. – virtualdj Aug 12 '21 at 13:27
  • @virtualdj are you sure it's not complaining about the fact that you passed --set language=eng lish as a single element?Have you triedMYSETTINGS+=(--set language=""${LANG_NAME}"")` – steeldriver Aug 12 '21 at 13:33
  • I've added another example below, to better clarify. And I need language to be quoted as well, i.e. --set "language=eng lish" or --set "name=my title" (with the new example). – virtualdj Aug 12 '21 at 13:42
  • The link is more useful, but doesn't explain how to deal with the double quotes inside a single element, i.e. --set "name=my title". So your answer doens't work, at the moment. – virtualdj Aug 12 '21 at 14:05
  • It's unlikely that they want the literal double quotes around $LANG_NAME. – Kusalananda Aug 12 '21 at 14:11
  • @Kusalananda actually it was the title name (see the question updated script), but mine was just an example... Basically anything that has a space. – virtualdj Aug 12 '21 at 14:18
  • @virtualdj Well, MYSETTINGS+=( --set language="$LANG_NAME" ) would do the right thing no matter what's in $LANG_NAME. The shell will keep language=whatever string together as one word. You then have to remember to use the array correctly: "${MYSETTINGS[@]}" (with the quotes!) If you want to add literal quotes, then you could obviously do that as shown, or you could just set LANG_NAME='"English (US)"' or whatever. – Kusalananda Aug 12 '21 at 14:21
  • @Kusalananda Mmm, you're right... if I use MYSETTINGS+=(--set "name=${TITLE_NAME}") (without the outer quotes) and mkvpropedit file.mkv "${MYSETTINGS[@]}" (with the quotes) it works, with the following output + mkvpropedit file.mkv --edit track:2 --set 'name=my title'. Please post it as an answer and I'll mark like so! I've tried all the combinations, minus this one :-( – virtualdj Aug 12 '21 at 14:26
  • @virtualdj Since steeldriver's answer here is mostly correct, we opted for modifying that instead of posting a new nearly identical answer. You should accept it if you believe it solve your issue. – Kusalananda Aug 12 '21 at 15:28
  • @Kusalananda I could accept it, but the answer must first be rewritten again for more clarity. The printf stuff should be moved away (separated) and the script rewritten to have mkvpropedit "${MYSETTINGS[@]}" (with the quotes) together with MYSETTINGS+=(--set "name=${TITLE_NAME}") (withouth the outer quotes and the escaped inner quotes). Now this information is present in the answer, but not in the correct order/together. – virtualdj Aug 12 '21 at 16:16
  • @steeldriver I'll leave that for you to decide on. I'm not writing an answer to this as I personally think yours is adequate and shows the general gist of things. – Kusalananda Aug 12 '21 at 16:25
  • @virtualdj, your problem aren't the single quotes Bash includes in the output of set -x. Your problem are the double quotes you add to the array elements. If you would run the command from the command line as mkvpropedit file.mkv --edit track:2 --set "name=my title", then you add those args to array as array=(--edit track:2 --set "name=my title"). – ilkkachu Aug 12 '21 at 20:45
  • The quotes there are part of shell syntax, and they work to include the space as a literal part of the string (argument to the command or array element, doesn't matter). Without them, that would be split to two strings. With \", you'd add a literal quote to the argument string, which the program probably doesn't want. They seldom do. Almost in no case you need to pass literal quotes to any program. – ilkkachu Aug 12 '21 at 20:45
  • @virtualdj, there's an example here about using an arg with whitespace in it. If it's still unclear, please comment so we can get better examples there. – ilkkachu Aug 12 '21 at 20:54
-2

[Wrong]Escape dollar sign:

MYSETTINGS+=("--set name=\${TITLE_NAME}")

Or:

MYSETTINGS+=('--set name=${TITLE_NAME}')
mkvpropedit file.mkv ${MYSETTINGS[@]}

For the variable to be expanded when executing the command

./test.sh
+ mkvpropedit file.mkv --edit track:2 --set 'name=${TITLE_NAME}'
The file is being analyzed.
The changes are written to the file.
Done.
nezabudka
  • 2,428
  • 6
  • 15