Can command substitution be nested in variable substitution?

I would like to use variable substitution on a particular string that I access via a command. For example, if I copy something into my clipboard, I can access it like this.

$ xclip -o -selection clipboard
Here's a string I just copied.

If I assign it to a variable, then I can do variable substitution on it.

$ var=$(xclip -o -selection clipboard)
$ echo $var
Here's a string I just copied.
$ echo ${var/copi/knott}
Here's a string I just knotted.

However, is there a way to do variable substitution without assigning it to a variable? Conceptually, something like this.

$ echo ${$(xclip -o -selection clipboard)/copi/knott}
bash: ${$(xclip -o -selection clipboard)/copi/knott}: bad substitution

This syntax fails, because var should be a variable name, not a string.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

No, you can’t. bash and most other shells (except zsh) don’t allow nested substitution.

With zsh, you can do nested substitution:

$ echo ${$(echo 123)/123/456}   
456

Method 2

Yeah, you can do that – kind of. It’s really not pretty. It’s more like in-line than nested. The problem is you have to operate on the value of the parameter you expand – if that parameter has no value then you won’t do much. So, you can assign the value while expanding it and it’s hardly a shortcut.

v=; echo "${v:=${0##*["$0${v:=$(xsel -bo)}"]}${v/copi/knott}}"

I use the $0 param expansion within the chain to hide the assignment. It assigns the var’s value within a nested assignment expansion. The outer takes precedence – but because it would just expand to whatever the inner one does it’s hard to tell. However, if we silence the inner expansion, then modify it you can get what you want. After copying your string to my clipboard (I don’t have xclip – just xsel) it prints:

Here's a string I just knotted.

It’s a little clearer what’s going on if you leave $0 out, though:

v=; echo "${v:=${v:=$(xsel -bo)}${v/copi/knott}}"

That prints:

Here's a string I just copied.  Here's a string I just knotted.

…because the inner assignment occurs before the modification, but, as noted, the outer assignment takes precedence – and it expands to both the inner-assignment’s expansion and to the modified inner-expansion.

Of course none of that works at all if the targeted parameter is already assigned – so you can only do it surely if you empty the variable in the first place… which, honestly, is probably the most convenient time to assign it after all.

Method 3

If you don’t want to create a variable, then there are other ways to perform string substitution:

$ echo "$(xclip -o -selection clipboard | sed 's/copi/knott/')"
Here's a string I just knotted.

Method 4

Here’s an examplary hack, that bypasses assigning a temporary variable for chained parameter substitution:

declare -a values=( "4: " "#1" "#2" "#3" "#4" )
declare -A arr["VALUES"]="${values[@]}"

echo -e "Original: ${arr[@]}"

"${arr[@]#*: ?}" >&/dev/null; san="${_//#/}"

echo -e "Sanitized: $san"

Explanation:

We initialize an associative array arr with values from array values.
The first element contains the number of value elements, followed by a :; this needs to be filtered out, as well as the prefix # for all values.

"${arr[@]#*: ?}" >&/dev/null

This is a parameter substitution, that first expands the values of arr and filters everything in front, up to and including '4: ' (#*: ?), discarding the result, both of stdout and stderr. This also suppresses an error, too, due to the missing variable assigning of the result.

>& is a short form of 2>&1, assigning the stderr output (fd2) to stdout (fd1) and both to /dev/null.

The trick now is, to use bash’s special underscore _ parameter, which copies the discarded result parameter from the last command (1st parameter expansion operation above and result redirection to /dev/null) into our second parameter substitution, where the # value prefixes are filtered:

san="${_//#/}"

Output:

Original: 4: #1 #2 #3 #4

Sanitized: 1 2 3 4

Also see: Special parameters and shell variables

Method 5

On Linux, you can pipe into the source command:

echo $(xclip -o -selection clipboard)/copi/knott|source /dev/stdin

For systems that do not have /dev/stdin, you would have to write to
a temporary file, then source it.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x