1

After a couple of hours trying to get my sed query to work, I'm about to give up!

I have the following string, extracted from source code - the aim is to generate prototypes for a lot of old and undocumented code. For example:

function foo(bar=1);

I want to end up with something like :

function foo(
    bar=1)

I want to match all lines that start with function followed by a random alphanumerical (and - and _) word, and add line breaks and tabs with sed.

My problem is that sed is greedy by default, and I cannot get sed to add a line break after the first ( due to regex greedyness.

So something like this hardcoded works:

echo 'function foo(bar=true)' | sed 's:\(function foo(\)\(.*\):\1\n\t\2:g'

which gives me the expected output:

function foo(
    bar=true)

I can modify this to the following, adding the newline after the ( character:

echo 'function foo(bar=true)' | sed 's:\(function.*(\)\(.*\):\1\n\t\2:g'

Which gives the same expected result as before - until I get to a function in the code that contains an array as a default value for an argument - which is where the greedy regex trips me up:

echo 'function foo(bar=array())' | sed 's:\(function.*(\)\(.*\):\1\n\t\2:g'

Which actually gives:

function foo(bar=array(
    ))

The greedyness causes the newline and tab to be added after the last ( and not the first. Unfortunately sed does not support non greedy regex which would solve everything immediately...

So I tried trying to make a regex that would do something like below, but I'm not getting anywhere:

  • 's:\(function [\w+]\)\(.*\):\1\n\2:g' trying to extract an alpha numericals which should match everything to the first (
  • Doing the same trying using an :alnum: class to match the word
  • Doing the same replacing with the more sed friendly [A-Za-z0-9_-] but having difficulties understanding how to get this to match more than 1 character in the pattern, so it takes the word up to the first ( - and then get the rest in the second return.

It seems that these character classes are just being ignored in the query, and I'm out of ideas.

As I cannot make sed non-greedy, how can I match a string which is of the format

KnownKeyword SomethingRandomAlphaNumerical-_(SomethingElse())

into a string that after line breaking after the first (, will look like:

KnownKeyword SomethingRandomAlphaNumerical-_(
SomethingElse())

Where am I going wrong? What pattern would accomplish this?

3 Answers 3

1

Do not use a dot . as the "any character", use a negative character match [^(]. So, your regex would be:

$ echo 'function foo(bar=array())' | sed 's:\(function[^(]*(\)\(.*\):\1\n\t\2:g'

function foo(
    bar=array())

A negative match will match any character except the ones inside the brackets after the initial ^ (and except newlines). What this means is that a [^(] could be understood as the match "not (". Then there is an * which means repeating as many times as possible, it is still greedy but won't match a (. In short: it will match every character up to the next (. This technique is to limit the greediness of .* by giving it a limiting character.

4
  • I've never used a negative character match before, so this is interesting! Just to be sure I'm understanding this correctly - insert more coffee please - the first capture is matching function up to the first ( but excluding it from the capture itself due to the negative match? For my personal education, can you explain why this is not trying to be greedy and blowing through to the second (( in the matched string or is that due to the .* I was using - I admit that I've gotten into a habit of using the .* pattern as *on it's own sometimes only matches one character!
    – Daniel
    Commented Sep 17, 2022 at 7:54
  • Unfortunately I'm too 'new' to upvote, but this does work very well! Thank you very much for your time!
    – Daniel
    Commented Sep 17, 2022 at 8:18
  • Please read the edit. @Daniel Commented Sep 17, 2022 at 14:29
  • Thank you for the extra explanation!
    – Daniel
    Commented Sep 18, 2022 at 10:10
0

Match lines starting with function, sub the first opening parenthesis, add a new line and tab using GNU sed

$ sed '/^function/s/(/&\n\t/' input_file
function foo(
    bar=array())
2
  • Probably the simplest solution and wondering why I didn't try this! Basic sed. Given my previous tests trying to capture the output and rebuild, this is where the regexes were causing problems in the match/replace. I'm guessing that from there, the first string up to the first ( is substituted with the string itself plus \n\t, and as the rest of the line with possible matches is now on a newline, there is nothing left to substitute on this line?
    – Daniel
    Commented Sep 17, 2022 at 8:11
  • @Daniel Yes, that is correct, the parenthesis is returned with a new line and tab, everything else after will then follow.
    – sseLtaH
    Commented Sep 17, 2022 at 8:29
0

To match your requirements precisely, the function identifier[1] is (alphanumeric or underscore or hyphen) characters:

sed -E 's:function[[:blank:]]+[[:alnum:]_-]+\(:&\n\t:' file`

there's no need to capture anything, just match "function" up to the open parentheses, and use & to refer to the matched text in the replacement string.

[1] Although many language restrict the identifier: the first character is limited to alpha or underscore.

2
  • This one works too :) I'm comparing this to another test I've still got open in my shell, as I've got probably 50 permutations that I could not get to work! sed 's:\(function [[:alnum:]_]+\)\(.*\):\1\n\2:g' Looks like I was close but dead set on pattern capture rather than just replace and pull the start of the line in to the result!
    – Daniel
    Commented Sep 17, 2022 at 8:04
  • Again, thank you for this answer, it works, but I'm too new here to give you an upvote! Have a virtual +1 and my thanks!
    – Daniel
    Commented Sep 17, 2022 at 8:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .