0

I have a code:

1 /**             
2 a        b         c
3 **/
4 int main() {
5     int x;
6     if ( condition) {
7     return       x;
8     }
9 }

I need to change multiple whitespaces between tokens or strings to single whitespace eg in line 7 but comments (line 2) should not be affected nor the leading tabs in the code. So, the output should be:

1 /**             
2 a        b         c
3 **/
4 int main() {
5     int x;
6     if ( condition) {
7     return x;
8     }
9 }

I tried using 'tr': ~$ tr -s " " < file but it changed line 2 as well as removed leading tabs in line 5 to line 8. Can it be done using sed?

4
  • 2
    Parsing C comments is the hard part here.
    – ilkkachu
    Sep 24, 2018 at 4:33
  • I think it will have to be greedy like cat input | sed 's/(return)( *)(x)/\1 \3/g' > output
    – Windy Day
    Sep 24, 2018 at 4:43
  • 1
    What should happen with printf("some spacey words\n"); where the words have more than one space between them? (the markup here removes the multiple spaces between the words).
    – Kusalananda
    Sep 24, 2018 at 6:31
  • 3
    I think you'd be better off with using a proper C code formatter such as indent or clang-format.
    – Kusalananda
    Sep 24, 2018 at 6:35

3 Answers 3

1

How far would

sed -rn '\#/\*\*#,\#\*\/*# {p;b}; s/([^ ]) +/\1 /g; p' file

get you? It prints the comment lines unmodified (can't deal with comment on / off in the same line, though) and skips the rest of the script. For uncommented lines, it squeezes any multiple spaces following a non-space char (so line indentation is out of scope) to a single space.

1
  • great, worked ! An explanation would be better.
    – Windy Day
    Sep 26, 2018 at 11:29
0

Finally, this worked for me:

sed -i 's/\([a-zA-Z]\+\)\( *\)\([a-zA-Z]\+\)/\1 \3/g' $1

2
  • This doesn't seem to leave the comments alone?
    – RudiC
    Sep 26, 2018 at 13:45
  • but in mine it does, I dont know how. I had a deadline so this worked.
    – Windy Day
    Sep 27, 2018 at 7:19
0

Using Raku (formerly known as Perl_6)

~$ raku -pe 's/^ [\d+ " "?] \t* \H+? <(" " ** 2..*)> / /;'  file

#OR

~$ raku -pe 's/^ [\d+ " "?] \c[TAB]* \H+? <(\c[SPACE] ** 2..*)> / /;'  file

The Raku code above quite literally differentiates between tabs (\t or \c[TAB] and spaces (" " or \c[SPACE]).

The -pe flags run code linewise over the input, with autoprinting. The regex searches from the ^ beginning-of-the-string for [\d+ " "?] digits followed by 0-or-1 space, followed by an optional tab, followed (non-greedily) by \H+? one-or-more non-horizontal-whitespace characters, followed by 2..* two-or-more spaces. The <(...)> capture markers drop everything from the match except the final two-or-more spaces, which are replaced with a single space.

Omit the bracketed [\d+ " "?] group if no line numbers. Only one substitution per line, so may have to be run multiple times if multiple spaced characters at the right end of the line.

Sample Input:

1 /**             
2 a\tb\tc
3 **/
4 int main() {
5\tint x;
6\tif ( condition) {
7\treturn       x;
8\t}
9 }

Sample Output:

1 /** 
2 a\tb\tc
3 **/
4 int main() {
5\tint x;
6\tif ( condition) {
7\treturn x;
8\t}
9 }

Of course, the answer above is predicated upon characters-within-comment-blocks being separated by tabs, instead of multiple-spaces. Maybe the best way to ensure that is to pre-run a substitution for comment blocks specifically:

~$ raku -pe 'state $ph;                     \
             $ph = 1 if /^  "/**"  \s* $/;  \
             $ph = 0 if /^  "**/"  \s* $/;  \
             s:g/" " ** 4/\t/ if  $ph == 1 ;'   file

The code above will substitute one tab for every 4 spaces, only within comment blocks. Add back in the bracketed [\d+ " "?] group just after the ^ in the second and third statements, if each line starts with a line-number.

https://unix.stackexchange.com/a/701572/227738
https://docs.raku.org/language/regexes
https://raku.org

You must log in to answer this question.

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