0

Shell: GNU BASH

I've been using the following command for a while now:

echo "$(fmt -w 50 < foo.txt)" > foo.txt

Without any issues. However, when I try:

fmt -w 50 < foo.txt > foo.txt

The file foo.txt gets turned into a blank file. I assume that this is because of something to do with precedence of redirection operators and subshells being parsed by the shell.

Searching the GNU BASH manual hasn't been very fruitful. Could someone please tell me if I missed something in the manual? I would really like to understand the reason for this behaviour so I can make further use of it and know about any cases where it might lead to trouble.

2 Answers2

2

From the Bash manual, 3.7.1 Simple Command Expansion:

When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.

  1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
  2. The words that are not variable assignments or redirections are expanded (see Shell Expansions). If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments.
  3. Redirections are performed as described above (see Redirections).
  4. The text after the = in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.

Command substitution is one of the expansions that take place in (2), and redirection takes place after that in (3). This doesn't have anything to do with subshells, it's purely due to the order in which the shell goes about doing things. (fmt -w 50 < foo.txt) > foo.txt would have resulted in a blank file too.

muru
  • 72,889
2

One could use the sponge util:

fmt -w 50 < foo.txt | sponge foo.txt

A note on the original:

echo "$(fmt -w 50 < foo.txt)" > foo.txt
  1. First executed is fmt, its output gets put in a string, i.e. what's in the double quotes. Nothing else happens until fmt finishes.

  2. echo prints the string to STDOUT.

  3. Meanwhile echo's output is redirected with > to foo.txt.

Steps #2 and #3 occur more-or-less simultaneously.

The original code does have an issue. Command lines have length limits. If the input foo.txt is very large, specifically larger than the length of getconf ARG_MAX, the echo command will either fail or give incomplete results.

agc
  • 7,223