Usando a versão GNU de todas as 3 ferramentas (role para baixo para ver as tentativas do FreeBSD), se eu quisesse encontrar '
na entrada usando o awk com um '
script delimitado por -, poderíamos tentar corresponder usando as sequências de escape hexadecimais e octais:
$ echo "'" | awk '/\x27/'
'
$ echo "'" | awk '/\047/'
'
$ echo "'" | awk '/\o047/'
awk: cmd. line:1: warning: regexp escape sequence `\o' is not a known regexp operator
então os 2 primeiros funcionam e o 3º não, como você esperaria intuitivamente.
Agora vamos tentar o mesmo com sed (com ou sem -E
):
$ echo "'" | sed -n '/\x27/p'
'
$ echo "'" | sed -n '/\047/p'
$
$ echo "'" | sed -n '/\o047/p'
'
e grep (também com ou sem -E
):
$ echo "'" | grep '\x27'
grep: warning: stray \ before x
$ echo "'" | grep '\047'
grep: warning: stray \ before 0
$ echo "'" | grep '\o047'
grep: warning: stray \ before o
Então:
- Mais importante: por que eles são diferentes?
- Curiosidade secundária: Existe uma maneira de usar uma sequência de escape no grep para corresponder
'
sem recorrer à opção não portátil do GNU grep-P
e sem expandir a sequência de escape antes que o grep a veja usando construções de shell comogrep $'\047'
?
Vale ressaltar que octal \047
é a sequência de escape recomendada no awk (veja http://awk.freeshell.org/PrintASingleQuote ou https://web.archive.org/web/20230530010453/http://awk.freeshell.org/PrintASingleQuote se estiver inativo).
Para os propósitos desta questão, não estou interessado em alternativas que permitam literal '
ou o que qualquer outra ferramenta faz ou qualquer outra coisa, estou apenas tentando descobrir por que essas 3 ferramentas específicas de correspondência de regexp tratam sequências de escape ASCII de forma diferente umas das outras. Eu estaria, no entanto, interessado em aprender como o BSD ou outras variantes dessas 3 ferramentas se comportam, dados os mesmos scripts mostrados acima.
Informações adicionais:
FreeBSD
Este é o comportamento do FreeBSD 13.1:
% echo "'" | awk '/\x27/'
'
% echo "'" | awk '/\047/'
'
% echo "'" | sed -n '/\x27/p'
'
% echo "'" | sed -n '/\047/p'
% echo "'" | sed -n '/\o047/p'
sed: 1: "/\o047/p": RE error: trailing backslash (\)
% echo "'" | grep '\x27'
grep: trailing backslash (\)
% echo "'" | grep '\047'
%
POSIX
Veja o que os padrões POSIX para Expressões Regulares e as 3 ferramentas em questão dizem sobre isso:
- expressões regulares: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap09.html
awk
: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/awk.htmlsed
: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/sed.htmlgrep
: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/grep.html
Da especificação regexp vemos que nem x
nem 0
são "Caracteres Especiais" em um BRE ou ERE , portanto são "Caracteres Ordinários" e que
Quando não está dentro de uma expressão entre colchetes, a interpretação de um caractere comum precedido por um caractere sem escape é indefinida, exceto para:
seguido por listas de caracteres, nenhum dos quais inclui 0
ou x
para BREs ou EREs, então minha conclusão é que nem \x27
nem \047
são comportamentos definidos em uma expressão regular por POSIX.
A seção Expressões Regulares da especificação POSIX awk diz:
\ddd
Um caractere seguido pela sequência mais longa de um, dois ou três caracteres de dígito octal (01234567). Se todos os dígitos forem 0 (ou seja, representação do caractere NUL), o comportamento é indefinido. Se os dígitos produzirem um valor maior que o octal 377, o comportamento é indefinido.
então sabemos que \0
é definido para um awk POSIX, mas \x
não é, então o comportamento do awk \x
não é definido pelo POSIX para o awk e, portanto, é deixado para as várias implementações do awk.
A seção Expressões regulares da especificação POSIX sed adiciona algumas alterações ao regexp, mas não menciona \0
or \x
e remete às definições de regexp POSIX e, portanto, \0
or \x
não são definidos pelo POSIX para sed.
A seção Descrição da especificação grep do POSIX se refere inteiramente às definições de expressões regulares do POSIX e, portanto \0
, \x
não são definidas pelo POSIX para grep.
Então, aparentemente, o significado de \xdd
é deixado para os implementadores de ferramentas para grep, sed e awk, enquanto o significado de \0dd
é definido para awk, mas deixado para os implementadores de grep e sed.
Manuais GNU
A seção Sequências de escape do manual do GNU awk diz:
\nnn
O valor octal nnn, onde nnn representa de 1 a 3 dígitos entre '0' e '7'. Por exemplo, o código para o caractere ASCII ESC (escape) é '\033'.
\xhh…
O valor hexadecimal hh, onde hh representa uma sequência de dígitos hexadecimais ('0'–'9', e 'A'–'F' ou 'a'–'f'). São permitidos no máximo dois dígitos após '\x'...
então é aí que \x47
o GNU awk é definido.
A seção Sequências de Escape do manual do GNU sed diz:
\oxxx
Produz ou corresponde a um caractere cujo valor ASCII octal é xxx.
\xxx
Produz ou corresponde a um caractere cujo valor hexadecimal ASCII é xx.
então é onde \o047
e \x27
são definidos para o GNU sed.
O manual do GNU grep não contém nenhuma referência que eu tenha encontrado sobre sequências de escape hexadecimais ou octais, o que explica as mensagens de aviso que vemos quando tentamos usá-las e provavelmente significa que elas simplesmente não são suportadas no GNU grep.
\1
,\2
,\3
,\4
... são usados para referências anteriores em expressões regulares básicas (BRE do final dos anos 60, como encontrado emed
,grep
,sed
...).Expressões regulares estendidas introduzidas
egrep
no final dos anos 70 com um novo algoritmo de expressão regular não tinham (e não poderiam ter com esse algoritmo) suporte de referência anterior.awk
contemporâneo aegrep
, com uma linguagem moldada a partir daquelaC
usada em EREs desde o início, tinha literais de string semelhantes a C, dentro dos quais você podia ter\47
escapes octais (como em C), e não havia nada que impedisse que esses escapes também fossem adicionados nos/ERE/
literais de expressão regular, já que EREs não podiam ter referências posteriores.Fora de
awk
, nem POSIX BRE nem ERE suportam esses escapes. Only\n
é especificado parased
(como era historicamente suportado pelo originalsed
).\47
como uma sequência de escape para o byte 0x27 definitivamente não pode ser adicionada a BREs, pois entraria em conflito com referências anteriores. Como muitas implementações ERE adicionaram suporte para referências anteriores desde o final dos anos 70, adicioná-lo ao ERE também não é mais uma opção. É uma chatice que a maioriaawk
não suporta referências anteriores e, naqueles que suportam, como o busybox, você tem que fazerawk '$0 ~ "^(.*)\\1$"'
for o equivalente agrep -x '\(.*\)\1'
(notawk '/^(.*)\1$/'
as that\1
is^A
instead andawk '/^(.*)\\1$/'
is para corresponder a algo que termina em\1
).Observe que a sintaxe em todas as ferramentas, exceto
echo
para aquelas sequências octais (inicialmente possivelmente de C), é\
seguida por 1 a 3 números octais, não há necessidade de um 0 inicial0
e você não pode ter um 0 inicial para números de bytes acima de 63 (077). (\0377
em qualquer coisa, excetoecho
é\037
(^_
) seguido por7
), então enquanto\047
não entra em conflito com referências anteriores, pois\0
não é uma referência posterior válida (pelo menos no POSIX BRE, há alguns onde\0
significa a correspondência completa),\377
entraria.O
\xHH
é do C89 (ANSI C), também encontrado em perl desde perl 4 (em seus literais de string e literais regexp lá). Ele não tem aquele problema de conflito com referências posteriores, mas não é suportado por todos os mecanismos regexp ainda³. Em C,\x
pode ser seguido por qualquer número de dígitos hexadecimais, já quechar
s em ANSI C não precisa ser de 8 bits. Emperl
, apenas até 2 dígitos são aceitos, embora\x{HH}
possa ser estendido para\x{20AC}
¹ quando no modo Unicode também é suportado. Em outros lugares, quantos dígitos hexadecimais podem ser consumidos depois\x
varia com o aplicativo. Por exemplo, em ksh93,$'\x20AC'
não é byte0x20
(espaço em ASCII) seguido porAC
, mas o caractere U+20AC (€
), codificado em UTF-8².Os operadores de citação de shell
$'\47'
e$'\x27'
do ksh93 agora estão no POSIXsh
desde a edição de 2024 do padrão (sh
ainda não em todas as implementações; em particular, nãodash
como encontrados na maioria dos sistemas baseados em Debian).em qualquer caso, como o do awk
"\47"
(ou$'\x27'
4 ), que se expande para o byte 0x27, portanto, apenas em'
sistemas que usam ASCII como conjunto de caracteres base, então é esse que eu evito usar, pois não vejo sentido em introduzir uma dependência em uma codificação de caracteres específica.O
$'\u0027'
(or$'\u27'
or$'\U00000027'
) do zsh que se expande para o'
caractere quase chegou ao POSIX 2024sh
também, mas como houve algumas divergências e diferenças entre os shells quanto ao conjunto de caracteres para o qual eles deveriam se expandir (UTF-8 incondicionalmente à la ksh93, o charmap do local no momento em que o código é lido à la bash, o charmap do local no momento em que ele é executado à la zsh) e o que fazer quando o charmap não tem o caractere correspondente, sua inclusão foi adiada para a próxima versão principal.Em qualquer caso
'
específico, não há necessidade de especificar o código do ponto, pois ele\'
pode ser usado$'...'
para representar uma aspa simples.Com esses
\OOO
e\xHH
agora adicionados aosh
, há ainda menos motivos para que cada ferramenta individual adicione suporte a eles.Minha maneira preferida de incorporar
'
dentro de um argumento de código entre aspas simplessed
,awk
,perl
,sh
ou qualquer argumento entre aspas simples para qualquer comando, para esse caso, em shells do tipo POSIX, é inseri-lo\'
fora das aspas simples:Com
awk
, você pode fazer:Em
rc
ouzsh -o rcquotes
Em
fish
, você pode fazer:Mas isso significa que
'...'
não há citações totalmente fortes lá, então, como no caso do ksh93$'...'
, você tem que ficar atento ao\
interior, o que é um problema.Outra opção é fazer:
¹
\x{7fffffffffffffff}
Indo muito além do intervalo Unicode, estendendo o algoritmo de codificação UTF-8.² enquanto
$'\xe9'
expande para o byte 0xe9, não para a codificação UTF-8 de U+00E9 (é
). Isso corresponde ao comportamento do perl neste modo não-Unicode padrão, onde a codificação padrão é iso8859-1 (também conhecido como latin1, byte único), onde caracteres acima de 255 não existem, então o perl muda para UTF-8 para eles (e emite um aviso), mas no perl, quando no modo Unicode,"\xe9"
expande para a codificação UTF-8 de U+00E9, enquanto no ksh93,$'\xe9'
é o byte 0xe9, independentemente de estar em uma localidade UTF-8 ou não. Para gerar caracteres codificados em UTF-8 com base em seu ponto de código Unicode, é melhor usar o mais novo (do zsh)\uXXXX
(ou\UXXXXXXXXX
para aqueles pontos de código acima de 0xffff)³ No caso do FreeBSD
sed
que você mencionou, isso não é feito pelo mecanismo de expressões regulares; esses\xHH
s são expandidos (em vários lugares, não apenas em expressões regulares) antes de chamar os mecanismos de expressões regulares (semelhante ao que acontece se você fizer issosubject ~ "\47"
noawk
), e somente desde o FreeBSD 13.0.4 embora tenha cuidado para garantir que o que se segue não seja mais dígitos hexadecimais, pois o comportamento do ksh93
$'\x20ac'
se expande para,€
como mencionado acima, é permitido, embora não exigido pelo POSIX, então se você quiser o byte 0x27 seguido porAC
, você precisa$'\x27'$'AC'
, por exemplo (ou usar a forma octal que não tem o problema; o ksh93 suporta,$'\x{27}AC'
mas essa não foi especificada pelo POSIX e, como mencionado, raramente é encontrada.