Por que o awk entende FS = “*” mas não para FS = “- * -”?

3

Eu tenho um arquivo de teste, seu conteúdo é:

a -*- b

Eu usei awk 'BEGIN {FS="*"} {print $2}' test , imprime

- b

Correto! Mas quando eu uso awk 'BEGIN {FS="-*-"} {print $2}' test , eu tenho:

*

Eu sei que FS suporta regex, então eu adicionei \ antes de * , eu fiz awk 'BEGIN {FS="-\*-"} {print $2}' test ainda tenho:

*

Por sorte, eu tenho um blog escrito por mim há meio ano. Que mencionei que deveria usar awk 'BEGIN {FS="-[*]-"} {print $2}' test neste caso. Assim eu consegui:

 b

Corrija novamente!

Mas eu estava realmente confuso porque o FS pode entender * , não consigo entender -*- e -\*- e, finalmente, posso entender o -[*]- .

Qual é o mecanismo?

    
por Zen 06.02.2015 / 05:00

3 respostas

4

Se FS for maior que um único caractere, ele será tratado como uma expressão regular. Um FS de apenas * é visto como uma sequência fixa, mas uma FS de -*- é uma expressão regular e -*- é equivalente a -+ (um ou mais - ). Então você precisa fazer com que * seja considerado como um personagem regular. -\*- e -[*]- podem fazer isso. No entanto, a string para FS é analisada duas vezes - uma vez quando você a atribui e uma vez para dividir em FS . É por isso que \ -escaped characters precisam ter o \ também.

$ awk -F '-\*-' '{print $2,FS}' test.txt
 b -\*-
$ awk -F '-\*-' '{print $2,FS}' test.txt
awk: warning: escape sequence '\*' treated as plain '*'
* -*-
    
por 06.02.2015 / 05:26
3

Um ponto-chave na resposta de muru é que, para obter uma barra invertida no FS regex, você precisa escrever double backslash \ . Isso porque a barra invertida é usada como um caractere de escape em dois níveis diferentes.

Uma única barra invertida em uma string será tratada como escapando do caractere seguinte, por isso precisamos escapar da própria barra invertida para que possamos obter uma única barra invertida na regex. E então essa barra invertida irá escapar do seguinte caractere dentro da regex.

Como eu disse em um comentário, não há diferença entre FS='ax\*' e FS='ax*' porque \* é tratado como * , mas awk imprimirá um aviso para esse efeito. Se você quiser colocar um literal * no FS , será necessário usar barras invertidas duplas, por exemplo, FS='ax\*' será dividido em ax* .

Talvez alguns exemplos tornem tudo isso mais claro.

#!/usr/bin/env bash

s='123abcd
123axbcd
123axxbcd
123ax*bcd
123ax**bcd'

printf "%s\n\n" "$s"

awk -F 'ax*' 'BEGIN{printf "FS=[%s]\n", FS};{printf "[%s] [%s]\n", $1, $2}' <<< "$s"
echo

awk 'BEGIN{FS="ax*"; printf "FS=[%s]\n", FS};{printf "[%s] [%s]\n", $1, $2}' <<< "$s"
echo


awk -F 'ax\*' 'BEGIN{printf "FS=[%s]\n", FS};{printf "[%s] [%s]\n", $1, $2}' <<< "$s"
echo

awk 'BEGIN{FS="ax\*"; printf "FS=[%s]\n", FS};{printf "[%s] [%s]\n", $1, $2}' <<< "$s"
echo


awk -F 'ax\*' 'BEGIN{printf "FS=[%s]\n", FS};{printf "[%s] [%s]\n", $1, $2}' <<< "$s"
echo

awk 'BEGIN{FS="ax\*"; printf "FS=[%s]\n", FS};{printf "[%s] [%s]\n", $1, $2}' <<< "$s"
echo

saída

123abcd
123axbcd
123axxbcd
123ax*bcd
123ax**bcd

FS=[ax*]
[123] [bcd]
[123] [bcd]
[123] [bcd]
[123] [*bcd]
[123] [**bcd]

FS=[ax*]
[123] [bcd]
[123] [bcd]
[123] [bcd]
[123] [*bcd]
[123] [**bcd]

awk: warning: escape sequence '\*' treated as plain '*'
FS=[ax*]
[123] [bcd]
[123] [bcd]
[123] [bcd]
[123] [*bcd]
[123] [**bcd]

awk: warning: escape sequence '\*' treated as plain '*'
FS=[ax*]
[123] [bcd]
[123] [bcd]
[123] [bcd]
[123] [*bcd]
[123] [**bcd]

FS=[ax\*]
[123abcd] []
[123axbcd] []
[123axxbcd] []
[123] [bcd]
[123] [*bcd]

FS=[ax\*]
[123abcd] []
[123axbcd] []
[123axxbcd] []
[123] [bcd]
[123] [*bcd]
    
por 06.02.2015 / 07:00
1

Dentro do delimitador " , você precisa escapar da barra invertida mais uma vez.

$ echo 'a -*- b' | awk 'BEGIN {FS="-\*-"} {print $2}'
 b

Como estamos passando um regex para a variável FS, \ dentro das aspas duplas é analisado como barra invertida única e, em seguida, aplica a regex resultante contra a cadeia de entrada.

    
por 06.02.2015 / 05:03