3

I'm trying to sort my bash functions inside my .bash_functions file.

Here is what I type :

:g/^function /,/^}$/ s/\n/@@@
:sort /^function /
:%s/@@@/\r/g

EDIT0 : Here is some of the input :

function listVideosFromRSSPodCastPlayList {
    test $# = 1 && {
        local rssURL="$1"
        local wget="$(which wget2 2>/dev/null || which wget)"
#       echo $(youtube-dl -g "$rssURL")
        echo $(curl -s "$rssURL" | egrep -o "https?:[^ <>]*(mp4|webm)" | grep -v .google.com/ | uniq)
    }
}
function getVideosFromRSSPodCastPlayList {
    test $# = 1 && {
        local rssURL="$1"
        local wget="$(which wget2 2>/dev/null || which wget)"
#       $wget $(youtube-dl -g "$rssURL")
        $wget $(curl -s "$rssURL" | egrep -o "https?:[^ <>]*(mp4|webm)" | grep -v .google.com/ | uniq)
    }
}
function jpgRotate {
    test $# = 0 && {
        echo "=> Usage: $FUNCNAME angle file1 file2 file3 ..." >&2
        return 1
    }

    local angle=$1
    shift
    for pic
    do
        extension="${pic/*./}"
        newFile="${pic/.$extension/_ROTATED.$extension}"
        echo "=> Losslessly rotating $pic by $angle degrees into $newFile ..." >&2
        jpegtran -perfect -rotate $angle "$pic" > "$newFile"
        touch -r "$pic" "$newFile"
    done
}

Something is wrong with my first command because the functions' code should be converted into one liners.

However, here is a little piece of the output I get instead :

function listVideosFromRSSPodCastPlayList {@@@  test $# = 1 && {@@@     local rssURL="$1"@@@        local wget="$(which wget2 2>/dev/null || which wget)"@@@#       echo $(youtube-dl -g "$rssURL")@@@      echo $(curl -s "$rssURL" | egrep -o "https?:[^ <>]*(mp4|webm)" | grep -v .google.com/ | uniq)@@@    }@@@}@@@function getVideosFromRSSPodCastPlayList {
    test $# = 1 && {
        local rssURL="$1"
        local wget="$(which wget2 2>/dev/null || which wget)"
#       $wget $(youtube-dl -g "$rssURL")
        $wget $(curl -s "$rssURL" | egrep -o "https?:[^ <>]*(mp4|webm)" | grep -v .google.com/ | uniq)
    }
}
function jpgRotate {@@@ test $# = 0 && {@@@     echo "=> Usage: $FUNCNAME angle file1 file2 file3 ..." >&2@@@       return 1@@@ }@@@@@@ local angle=$1@@@   shift@@@    for pic@@@  do@@@       extension="${pic/*./}"@@@       newFile="${pic/.$extension/_ROTATED.$extension}"@@@     echo "=> Losslessly rotating $pic by $angle degrees into $newFile ..." >&2@@@       jpegtran -perfect -rotate $angle "$pic" > "$newFile"@@@     touch -r "$pic" "$newFile"@@@   done@@@}@@@function locateBin {
    local regExp="$1"
    shift
    locate "bin/.*$regExp" "$@"
}
...

Can you help me ?

2
  • Can you provide the original input? I suspect there are braces inside the functions, causing the schwartzian transform to not cover the entire text
    – D. Ben Knoble
    Commented Aug 13, 2019 at 5:08
  • As expected, you global command range falls short and/or @@@ is a bad delimiter. Maybe try ::: ?
    – D. Ben Knoble
    Commented Aug 13, 2019 at 12:07

1 Answer 1

4

Try to add the negative offset -1 to the second line specifier in the range passed to the first substitution command:

                   v
:g/^function /,/^}/- s/\n/@@@
:sort /^function /
:%s/@@@/\r/g

Otherwise, the newline at the end of your first function is replaced with @@@, which wrongly merges the first line of the second function with the last line of the first one.


the last substition does not work correctly for input lines already ending with @ (for example : echo $@)

In your substitution commands, you need to use a text which doesn't appear in your file. A good candidate for this kind of transformation is a non-printing character such as a control character like a literal C-a whose caret notation is ^A. Try this instead:

:exe "g/^function /,/^}/- s/\\n/\<c-a>"
:sort /^function /
:exe "%s/\<c-a>/\\r/g"

why bother with the "exe" command, extra quotes and extra backslashes which makes this harder to read ?

The original command that I posted worked on your input file. Now, you say that it doesn't work on another input file which contains echo $@, so I use a non-printing character for the replacement.

It works without the exe and the extra backslashes, but in this case your replacement is just the text <c-a>. And there's no guarantee it will work on some other input file you encounter in the future. OTOH, ^A is unlikely to appear in your bash script files 1, so there should be no issue.

If you agree, then you need exe, the quotes, and the extra backslashes to make Vim translate \<c-a> into ^A (see :h expr-quote).

You can see the result of the translation by replacing exe with echo:

:echo "g/^function /,/^}/- s/\\n/\<c-a>"
g/^function /,/^}/- s/\n/^A
                         ^^

:echo "%s/\<c-a>/\\r/g"
%s/^A/\r/g
   ^^

Edit: If you don't like the :exe and the extra backslashes, to express ^A you can write \%x01 in the pattern and \="\<c-a>" in the replacement of the substitutions:

:g/^function /,/^}/- s/\n/\="\<c-a>"/
:sort /^function /
:%s/\%x01/\r/g

1 If you need to write ^A in one of your bash script, try $'\ca'.

$ printf '%s' $'\ca' | od -tc
0000000 001
0000001

The syntax is documented in the QUOTING section from $ man bash:

Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded as follows:

...

\cx a control-x character

14
  • 1
    Something is wrong in the case where a line ends with $@, then after the first substitution command, this line ends with $@@@@ and the then the last substitution command replaces $@@@@ by \r@ instead of @\r, can you please update your last substitution command ?
    – SebMa
    Commented Aug 13, 2019 at 10:05
  • @SebMa you dont have to accepf an answer that doesnt work
    – D. Ben Knoble
    Commented Aug 13, 2019 at 12:06
  • @user938271 I'm sorry but it looks I accepted your answer too soon, the last substition does not work correctly for input lines already ending with @ (for example : echo $@)
    – SebMa
    Commented Aug 13, 2019 at 12:37
  • 1
    @SebMa A "placeholder" is nothing specific to Vim. I meant the three commercial-at signs, which you used as a marker for end-of-line. Commented Aug 13, 2019 at 15:59
  • 1
    So now, I post what I wanted to initially, because that's what I use all the time. I never use a temporary replacement using printing characters, because it caused me too many issues in the past, and because Vimscript has enough pitfalls to avoid. But again, use whatever you prefer.
    – user938271
    Commented Aug 13, 2019 at 17:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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