How to match the first POSIX `[:cntrl:]` character with older BSD `awk`?

Viewed 85

I wanted to match the first ASCII control character of an input string with awk but I stumbled upon what seems to be a bug on older BSDs:

#!/bin/bash

printf 'a \b c\n' |

awk 'match( $0, /[[:cntrl:]]/ ) { print RSTART }'
1

The expected output would be:

3

What could be wrong with the code?

4 Answers

What about using a character class designating control characters in this way with hex numbers:

$ printf $'a \b c\n' | awk 'match( $0, /[\x01-\x1f]/ ) { print RSTART }'
3

I can't test it as I don't have the old version of BSD awk you mentioned but would negating the [:print:] character class work there:

$ printf $'a \b c\n' | awk 'match( $0, /[^[:print:]]/ ) { print RSTART }'
3

I couldn't find a POSIX definition of [:print:] but according to the gawk manual:

[:print:] Printable characters (characters that are not control characters)

so in theory negating it might work.

  1. it's nawk that happens to show up in some BSD- descendant distributions, but it has nothing to do with bsd itself

  2. from my personal trial and error attempts, it seems nawk can gsub() a null byte properly into a replacement string, but there's no way to get nawk to print out a null byte, whether it's

-- coming in from /dev/stdin or files,

-- requested via sprintf("%c", 0),

-- or hard-coded into a variable, e.g. myvar = "\0" ;

nawk returns a 1 (true) for this test when all else returns false :

  nawk 'BEGIN { print ("\1"~"\0") }'

  1

nawk in its current form is also very screwy when it comes to invalid data inputs - a simple regex test for existence of a byte results in fatal error :

% nawk 'BEGIN { print length(_="\123\321\456\777") }' 
4

% nawk 'BEGIN { print length(_="\123\321\456\777"), (_~"\321") }'

nawk: multibyte conversion failure at ?...
 source line number 1
 context is
    BEGIN { print length(_="\123\321\456\777"), >>>  (_~"\321") <<< 

… a test that gawk in UTF-8 properly handles without any error messages:

LC_ALL='en_US.UTF-8' LANG='en_US.UTF-8' gawk -e '

    BEGIN { _="\123\321\456\777"; print (_~"\321") }' 

1

It seems like it is the NUL byte in [:cntrl:] that causes problems; if you put it at the end of the list, it works on BSD and GNU awk (but not with Solaris):

printf 'a \b c\n' |
awk 'match( $0, /[\x01-\x1F\x7F\x00]/ ) { print RSTART }'
3

While it isn't my intent, matching the NUL byte is still impossible, but it'll work on more recent awk. Be careful to not add anything else after \x00 though.

Related