9

I've read some other piping bash string manipulation questions but they seem to be specialized applications.

Essentially, is there a way to do the below simpler?

instead of

$ string='hello world'; string2="${string// /_}"; echo "${string2^^}"
HELLO_WORLD

something like

$ echo 'hello world' | $"{-// /_}" | "${ -^^}"
HELLO_WORLD

Edit I'm interested in staying within bash manipulations if possible to maintain speed (as opposed to sed/awk which have a tendency to greatly slow down my scripts)

Edit2: @jimmij

I like the second example and led me to making a function.

bash_m() { { read x; echo "${x// /_}"; } | { read x; echo "${x^^}"; }; }
echo hello world | bash_m
HELLO_WORLD
  • 1
    why do you think sed/awk would be slow for this purpose? They are as fast as they come. – mkc Oct 26 '14 at 3:09
  • 1
    @Ketan Sed and awk are separate processes, so they can never be fast as something that bash can do natively without launching a separate process. Normally this difference is hardly noticeable, but where performance matters in shell scripts is usually a certain loop or computation is being repeated a very large number of times, and spawning thousands of processes will be noticeably slower than doing a simple string manipulation in bash natively. – jw013 Oct 26 '14 at 3:50
  • 2
    @jw013 This is true for short strings as "hello world" from the question, but if string is very long, say the tr manual, then the opposite is true because time of spawning the processes is negligible in comparison to time of string manipulation for which sed and awk are dedicated. If string is extremely long, say the whole bash manual, then bash can just refuse to proceed altogether, because of some internal limitations. – jimmij Oct 26 '14 at 4:38
  • 2
    @jw013 I'm claiming that bash's string manipulation code is less efficient then dedicated tools as sed, awk, tr or similar. Look at the gena2x answer, which I edited some time ago adding exactly this information: unix.stackexchange.com/questions/162221/… you may want to compare it with terdon answer to the same question where he gives time for short strings in which case process spawning takes most time. You can test it yourself and post result. – jimmij Oct 26 '14 at 5:14
  • 1
    @Miati Why would you think this extra read x; echo $x is any better for performance? The syntax does not look any shorter or cleaner. x=${x// /_}; x=${x^^} is a much more concise way to do the same thing as {read x; echo ${x.... As far as performance goes, @jimmij has pointed out that tr / sed would be faster than bash, fork count being equal. Using a pipe always results in an extra process so the argument of saving a fork no longer applies. Thus, if are using pipes, just use sed / tr etc. If you can do it in bash, do so and skip this read x; echo $x nonsense. – jw013 Oct 27 '14 at 2:49
9

What jimmij said. His last example is the closest you can get to what you're attempting in your piped expression.

Here's a variant on that theme:

echo 'hello world'|echo $(read s;s=${s^^};echo ${s// /_})

I'd be inclined to use tr, as it's quite fast.

echo 'hello world'|tr ' [:lower:]' '_[:upper:]'

I suppose it's a shame that bash doesn't permit nested parameter expansion; OTOH, use of such nested expressions could easily lead to code that's painful to read. Unless you really need things to run as fast as possible it's better to write code that's easy to read, understand and maintain, rather than clever-looking code that's a PITA to debug. And if you really do need things to be done at top speed you should be using compiled code, not a script.

7

You cannot pass parameter expansions in such a way. When you refer to x using $ symbol as in "${x}" form, then it has to be real variable name, not a standard input, at least not in bash. In zsh you can perform nested parameter substitutions in the following way:

$ x=''hello world'
$ echo ${${x// /_}:u}
HELLO_WORLD

(note: :u is for zsh the same as ^^ for bash)

Nesting in bash is not possible and I think what you wrote in question is the best one can get, but if for any strange reason you need to involve pipes into equation then you may want to try this:

$ echo 'hello world' | { read x; echo "${x// /_}"; } | { read y; echo "${y^^}"; }
HELLO_WORLD
  • 1
    Considering our recent conversation about how tr / sed are faster than bash at string processing, and considering how you are using pipes to pass strings via standard I/O, I see literally zero point to doing those operations in bash as opposed to tr / sed. Why would one ever | { read x; echo $x... } as opposed to a | sed that does the same thing? – jw013 Oct 27 '14 at 2:54
  • 1
    @jw013 frankly speaking I see next to no point. It is just an example to forcefully engage pipes to the problem, because OP explicitly asked for them and didn't want to use external programs (both echo and read are bash built-ins, so in principle a little bit faster). As I already wrote in the answer progressive parameter manipulations which OP has in the question is the best one can get in my opinion for this task in bash. Anyway the problem is rather academic. – jimmij Oct 27 '14 at 3:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.