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
6.99k stars 333 forks source link

syntax: joins lines if escaped newlines aren't preceded by a space #953

Open ainar-g opened 1 year ago

ainar-g commented 1 year ago

In my scripts, I have a few commands that follow this general pattern:

env\
    VAR1='1'\
    VAR2='2'\
    somecommand\
    --big-flag-1='big_flag_value_1'\
    --big-flag-2='big_flag_value_2'\
    --big-flag-3='big_flag_value_3'\
    -x\
    -y\
    -z\
    file_with_a_very_long_name_1\
    file_with_a_very_long_name_2\
    file_with_a_very_long_name_3\
    file_with_a_very_long_name_4\
    ;

This is line-oriented to simplify diffing, sorting, etc. I would expect shfmt -p to either leave the lines as-is (maybe adding or removing a space before the final \), but the result is rather unexpected and weird:

env VAR1='1' \
       VAR2='2' \
       somecommand --big-flag-1='big_flag_value_1' \
       --big-flag-2='big_flag_value_2' \
       --big-flag-3='big_flag_value_3' \
       -x -y -z file_with_a_very_long_name_1 file_with_a_very_long_name_2 file_with_a_very_long_name_3 file_with_a_very_long_name_4

I get the addition of extra spaces, but then it also combines all of the single-letter flags and all non-option arguments into one unreadable line. But not the envs or the long-form flags.

I feel like shfmt shouldn't interfere with the placement of arguments on separate lines at all, but if it should, it should also at the very least not produce these overlong lines of mixed short options and positional arguments.

mvdan commented 1 year ago

This appears to happen because you are using \ at the end of each line instead of \ (with a space):

$ cat spaced.bash
env \
    VAR1='1' \
    VAR2='2' \
    somecommand \
    --big-flag-1='big_flag_value_1' \
    --big-flag-2='big_flag_value_2' \
    --big-flag-3='big_flag_value_3' \
    -x \
    -y \
    -z \
    file_with_a_very_long_name_1 \
    file_with_a_very_long_name_2 \
    file_with_a_very_long_name_3 \
    file_with_a_very_long_name_4 \
    ;
$ shfmt -d spaced.bash
[no diff]

I can't say why some arguments get a newline and some don't. I'm going to guess it's due to the same bug. Either way, there is a workaround for the bug - use spaces before your escaped newlines, which is the most common formatting as well.