mvdan / sh

A shell parser, formatter, and interpreter with bash support; includes shfmt
https://pkg.go.dev/mvdan.cc/sh/v3
BSD 3-Clause "New" or "Revised" License
7.21k stars 339 forks source link

syntax: allow printing redirections before all arguments #942

Closed mikez closed 1 year ago

mikez commented 1 year ago

Sometimes, to improve readability, I put logfile redirections at the beginning of the line:

>>$LOGFILE echo "[label]" 1 2 3
>>$LOGFILE some_command 4 5 6

This is currently, as of shfmt v3.5.1, incorrectly reformatted as follows:

echo >> $LOGFILE "[label]" 1 2 3
some_command >> $LOGFILE 4 5 6

Research

Putting redirects at various places in-between arguments and at the end seems to have been supported from the very beginning of sh in 1971. Putting redirects at the beginning was not supported in UNIX V1, but emerged sometime later. I haven't found out when.

UNIX V1 manual (1971-03-11) (and code):

Each command is a sequence of non-blank command arguments separated by blanks. [...] Two characters [i.e., "<" or ">"] cause the immediately following string to be interpreted as a special argument to the shell itself, not passed to the command. An argument of the form <arg causes the file arg to be used as the standard input file of the given command; an argument of the form “>arg” causes file “arg” to be used as the standard output file for the given command.

Bash documentation, section 3.6 (2022-11-13):

The following redirection operators may precede or appear anywhere within a simple command or may follow a command. Redirections are processed in the order they appear, from left to right. [...]

Bash documentation, section 3.7.1 (2022-11-13):

When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right, in the following order: 1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing. [...]

mvdan commented 1 year ago

The formatter wants to enforce a somewhat canonical and consistent format - it could be far more conservative and only fix up whitespace like indentation, but then it would be far less useful in practice :)

I realise that redirections can appear anywhere, but we currently only allow them after the first argument, or after all arguments. See https://github.com/mvdan/sh/issues/389 for a previous discussion.

I guess we could also allow them to appear before all arguments as a third option. That isn't a big change, and you're not the first person to suggest it (cc @dcsobral @ale5000-git), so perhaps I should reconsider.

One objective reason in favor of supporting it is that, in your example, keeping the redirections in the first place does help with alignment. In some other cases, the alignment could be better if the redirection was after the first argument - the user can choose in each instance. I don't think we should get into the business of writing a heuristic for it.

mikez commented 1 year ago

@mvdan Thank you for the reference to #389 and pointing out that >>a echo b is equivalent to echo >> b a. I had previously incorrectly understood that Bash only permitted the echo >>b a syntax, i.e., without space. Sorry for this.

In some other cases, the alignment could be better if the redirection was after the first argument - the user can choose in each instance. I don't think we should get into the business of writing a heuristic for it.

👍

Out of curiosity, do you have an example of these some other cases? For my use case, putting the redirection first, helps readability:

>>$LOGFILE echo "[label]" 1 2 3
…
>>$LOGFILE some_command 4 5 6
…
>>$LOGFILE do_this 7 8 9

is more easy to skim than this

echo >>$LOGFILE "[label]" 1 2 3
…
some_command >>$LOGFILE 4 5 6
…
do_this >>$LOGFILE 7 8 9

Most importantly, thank you for your continued work on shfmt. I use it a lot. 🌻

mvdan commented 1 year ago

If your redirections are all the same length, but the commands aren't, then placing them first helps with alignment:

>foo cmd1 args...
>foo cmd234 args...

In the opposite scenario, it helps to place the first argument at the beginning:

cmd >foo1 args...
cmd >foo234 args...
mikez commented 1 year ago

@mvdan Thank you.