Score:7

BASH: How can I combine two (or more) variable string manipulations in one step?

tr flag

Is it possible to combine two or more string manipulation functionalities of a variable in BASH (or any other standard Linux command)?

Let's say e.g. I have the variable $XDG_CURRENT_DESKTOP which holds the string ubuntu:GNOME.
Now, I can retrieve the second substring by ${XDG_CURRENT_DESKTOP##*:}GNOME.
I can also retrieve the lower case string through ${XDG_CURRENT_DESKTOP,,}ubuntu:gnome.

But how can I combine both functions (→ gnome) in onesimple – command without using a redirection to sed, awk, grep or any other of these – quite heavy weighted – commands and without an additional buffer storage step? e.g.:

local mybuffer="${XDG_CURRENT_DESKTOP##*:}"
echo "${mybuffer,,}"

I want to avoid such a "sub-script" or function call construct to achieve this and I already tried any combination of both mentioned but it seems to be futile.
Is there any other way?
Or do I need to upgrade bash? (using: GNU bash 4.3.11)
Or can any other shell do that?

hr flag
`zsh` can do it, with the `L` parameter expansion flag ex. `${(L)XDG_CURRENT_DESKTOP##*:}`
Jens avatar
tr flag
@steeldriver: Oohhh, nice! _THAT_ is exactly what I was looking for! Time for `zsh`. Thank you very much! :))
terdon avatar
cn flag
@steeldriver may as well post that as an answer since the OP also asked for other shells.
Jens avatar
tr flag
@terdon: d'accord
Score:4
cn flag

You could do this in bash if you use read to read two variables:

$ echo "$XDG_CURRENT_DESKTOP"
ubuntu:GNOME
$ IFS=':' read var1 var2 <<<"${XDG_CURRENT_DESKTOP@L}"
$ echo "$var2"
gnome

The ${variable@L} construct returns the value of $variable converted to lower case. Then, IFS=':' read sets the input field separator to : for the read command, this way the global IFS is left unchanged after the command exits, and then read var1 var2 will separate its input on : and save the result in the two variables var1 and var2. Note that if you have more than one : on the same line in the input, var1 will have everything up to the first : and var2 everything else. Finally, <<< is a "here string", a simple way to pass a variable as input to a command.

Jens avatar
tr flag
Nice workaround! But unfortunately this construct is way to long and presumably with to many background processes _(→ KISS principle)_. BTW, with this bash version I think I can adopt the combination similar to the zsh proposal from @steeldriver above. Anyway, 1+ for the inspiration!
terdon avatar
cn flag
@Jens it is long and unwieldy to write, absolutely. Zsh tends to be more elegant with this sort of thing. But many background processes? Why? This is all 100% bash builtin tools, I don't think there will be much (any?) forking involved.
Jens avatar
tr flag
Good point! I'm just trying to implement something like an _"uniform directive/constant"_ that really shouldn't take to much coding _and_ background processing. Meanwhile I anyway came to the point that `zsh` might be counterproductive due to the fact that it seems to be not widely distributed. So it doesn't make much sense.
terdon avatar
cn flag
@Jens zsh is becoming more and more popular, and is the default shell on some non-Ubuntu systems (macOS as far as I know, but maybe others) but yes, you cannot assume it will be available. But then you also cannot assume that bash is available :). For true portability, you need to stick to a basic POSIX shell like `sh` or `dash`. My answer depends on bashisms, the portable way would be to pipe to a portable external tool like `sed` or `perl`. All of this is off topic here though, we only handle Ubuntu. You might want to check out [linux.se] for portability questions.
Jens avatar
tr flag
sh, dash, bash, zsh, etc. … too many choices … I think I lost track of the global view with this detail ;oP I must admit, I did not consider this shell issue yet. Maybe for now I should stay with `bash`. From this perspective your code looks much better the more I think about it xD
Score:0
ng flag

Here's a bash5 (not bash4) script:

part2() {
   local -n var=$1
   var=${var#*:}
}
lc() {
   local -n var=$1
   var=${var,,}
}
fred="ubuntu:gnome"
part2 fred
lc fred
printf "%s\n" "$fred"

Output:

gnome

The functions part2 and lc modify the variable whose name is passed to them, without forking. In the end, the original variable (fred) has been modified. You still need the extra variable though.

The alternative, which should work on bash4, is something like this:

part2() { printf "%s" "${1#*:}"; }
lc()    { printf "%s" "${1,,}" ; }
fred="ubuntu:GNOME"
printf "%s\n" "$(lc "$(part2 "$fred")")"

which does not modify any variables (it only manipulates values), but it does create extra forks.

Jens avatar
tr flag
First of all: thank you for your effort. The result is fine. FYI: _all_ of your code is Bash4 in deed ;-). I really do appreciate modular programming, but in this case I was more looking for a single command code or alternatively a one-liner. Sorry, but thank you very much anyway.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.