18

In Bash version 4.2.47(1)-release when I try to catenate formatted text that comes from a HERE-dcoument like so:

cat <(fmt --width=10 <<FOOBAR
(I want the surrounding parentheses to be part of the HERE-document)
(Even the preceding unbalanced parenthesis should be part of it.
FOOBAR
) # I want this paranthesis to end the process substitution.

I get the following error:

bash: bad substitution: no closing `)' in <(fmt --width=10 <<FOOBAR
(I want the surrounding parentheses to be part of the HERE-document)
(Even the preceding unbalanced parenthesis should be part of it.
FOOBAR
)

Also I do not want to quote the HERE-document, i.e. write <'FOOBAR', because I still want to have variables being substituted within it.

Tim Friske
  • 2,190
  • 3
  • 23
  • 36
  • Do you really need the `cat` call? Why not leave it at calling `fmt`? – iruvar Jun 17 '14 at 00:52
  • 2
    I must admit it is a contrived example. My actual needs are more complex than that. – Tim Friske Jun 17 '14 at 00:55
  • 1
    It is interesting that when you replace `(Even` with `"(Even"` it works. It is same for `\(Even`. Looks like a parsing bug. Bash is still in a context were it is looking for braces while also in the context of reading the here doc and both contexts contradict each other. – Raphael Ahrens Jun 17 '14 at 08:40
  • 3
    This is fixed in `bash` 4.3, incidentally. – chepner Sep 09 '15 at 15:30

3 Answers3

8

The process substitution is roughly equivalent to this.

Example - mechanics of process substitution

Step #1 - make a fifo, output to it

$ mkfifo /var/tmp/fifo1
$ fmt --width=10 <<<"$(seq 10)" > /var/tmp/fifo1 &
[1] 5492

Step #2 - read the fifo

$ cat /var/tmp/fifo1
1 2 3 4
5 6 7 8
9 10
[1]+  Done                    fmt --width=10 <<< "$(seq 10)" > /var/tmp/fifo1

The use of parens within the HEREDOC also seems OK:

Example - just using a FIFO

Step #1 - output to FIFO

$ fmt --width=10 <<FOO > /var/tmp/fifo1 &
(one)
(two
FOO
[1] 10628

Step #2 - read contents of FIFO

$ cat /var/tmp/fifo1
(one)
(two

The trouble, I believe you're running into is that the process substitution, <(...), doesn't seem to care for the nesting of parens within it.

Example - process sub + HEREDOC don't work

$ cat <(fmt --width=10 <<FOO
(one)
(two
FOO
)
bash: bad substitution: no closing `)' in <(fmt --width=10 <<FOO
(one)
(two
FOO
)
$

Escaping the parens seems to appease it, a little:

Example - escaping parens

$ cat <(fmt --width=10 <<FOO                 
\(one\)
\(two
FOO
)
\(one\)
\(two

But doesn't really give you what you want. Making the parens balanced also seems to appease it:

Example - balancing parens

$ cat <(fmt --width=10 <<FOO
(one)
(two)
FOO
)
(one)
(two)

Whenever I have complex strings, such as this to contend with in Bash, I almost always will construct them first, storing them in a variable, and then use them via the variable, rather than try and craft some tricky one liner that ends up being fragile.

Example - use a variable

$ var=$(fmt --width=10 <<FOO
(one)
(two
FOO
)

Then to print it:

$ echo "$var"
(one)
(two

References

slm
  • 363,520
  • 117
  • 767
  • 871
8

This is an old question, and as you realize that this is a contrived example (and thus that the correct solution is to use cat | or actually, no cat at all in this case), I'll just post my answer for the general case. I would solve it by putting it in a function and using that instead.

fmt-func() {
    fmt --width=10 <<FOOBAR
(I want the surrounding parentheses to be part of the HERE-document)
(Even the preceding unbalanced parenthesis should be part of it.
FOOBAR
}

and then use that

cat <(fmt-func)
falstro
  • 413
  • 4
  • 8
  • 1
    This is a great solution to supplying scripts as files to interpreters (`awk`, `grep`, `node`, `python`, etc.) so that they can accept stdin for processing. It is also great for passing configs to commands like `aws` with the `--cli-input-json` flag. – Bruno Bronosky Aug 06 '20 at 10:19
  • This is exactly what I was looking for, a way to include short interpreter scripts in my bash scripts. I simply used `cat < – remcycles Mar 02 '22 at 21:31
3

This is just a workaround. Pipe fmt to cat instead of using process substitution

fmt --width=10 <<FOOBAR | cat 
(I want the surrounding parentheses to be part of the HERE-document)
(Even the preceding unbalanced parenthesis should be part of it.
FOOBAR
iruvar
  • 16,515
  • 8
  • 49
  • 81
  • 1
    I tried your "workaround" and it would work for me. Thanks. But still I want to grasp why my combination of a HERE-document nested inside a process substitution does not work. Have you got an answer? – Tim Friske Jun 17 '14 at 01:01
  • @TimFriske, I'm going to have to defer that one to one of the `bash` wizards on this site. My knowledge of bash parser internals is limited to say the least – iruvar Jun 17 '14 at 01:07