Prompt-doc: SED e Expressões Regulares

Sucesu - Curitiba, 18 de Fevereiro de 2003

Este é o histórico da linha de comando de uma palestra que fiz na Sucesu-PR para uma turma de 20 alunos, sobre SED e Expressões Regulares.

  • Data: 18-Fev-2004
  • Horário: 18:30
  • Duração: 4 horas
  • Local: Auditório da Sucesu-PR
  • Participantes: 20 pessoas
  • Requisitos: Nenhum

SED

O SED é o Stream EDitor, um editor de textos não interativo, automático, um filtro.

O Arquivo de dados

$ seq 12 > numeros.txt
$ vi numeros.txt
$ cat numeros.txt
1:um:one
2:dois:two
3:tres:three
4:quatro:four
5:cinco:five
6:seis:six
7:sete:seven
8:oito:eight
9:nove:nine
10:dez:ten
11:onze:eleven
12:doze:twelve

As origens do SED

$ ed
$ ed numeros.txt

O comando que todos conhecem: s///

$ echo aula
$ echo aula | sed s/a/A/
$ cat numeros.txt
$ sed s/e/E/ numeros.txt
$ sed s/e/E/g numeros.txt   # modificador 'g' de Global (troca todas)

O comando d (delete)

$ cat numeros.txt | sed 5d
$ cat numeros.txt | sed 1d
$ cat numeros.txt | sed 10d
$ cat numeros.txt | sed $d                     # ERRO! variável $d
$ cat numeros.txt | sed '$d'                   # _sempre_ usar aspas no shell

O comando y (transliterate)

$ cat numeros.txt | sed 'y/aeiou/AEIOUL/'
$ cat numeros.txt | sed 'y/aeiou/AEIOU/'
$ cat numeros.txt | tr a-z A-Z
$ cat numeros.txt | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'
$ cat numeros.txt | sed 'y/abcdefghi/iejsyfnch/'    # mini encriptador
$ cat numeros.txt | sed 'y/abcdefghi/iejsyfnch/' | sed 'y/iejsyfnch/abcdefghi/'
$ cat numeros.txt | tr abcdefghi iejsyfnch

O comando p (print)

$ cat numeros.txt | sed 5p                     # duplica
$ cat numeros.txt | sed -n 5p                  # só mostra a 5a linha

Brincando com a opção -n

$ cat numeros.txt | sed -n s/nove/nnn/
$ cat numeros.txt | sed -n s/nove/nnn/p
$ cat numeros.txt | sed -n p
$ cat numeros.txt | sed -n 5p

Começando a endereçar

$ cat numeros.txt | sed s/nove/nnn/
$ cat numeros.txt | sed 5s/nove/nnn/
$ cat numeros.txt | sed '5 s/nove/nnn/'        # endereço junto ou separado
$ cat numeros.txt | sed '5      s/nove/nnn/'   # vários espaços e/ou TAB
$ cat numeros.txt | sed '999s/nove/nnn/'
$ cat numeros.txt | sed '5d'
$ cat numeros.txt | sed '5,8 d'                # um intervalo de 4 linhas

Múltiplos comandos!

$ cat numeros.txt | sed '5d 7d 9d'             # errado
$ cat numeros.txt | sed '5d ; 7d ; 9d'         # certo

Os comandos são seqüenciais

$ cat numeros.txt | sed -n 's/nove/nnn/p'
$ cat numeros.txt | sed -n 's/nove/nnn/p ; s/nove/NNN/p'
$ cat numeros.txt | sed -n 's/XXXX/nnn/p ; s/nove/NNN/p'
$ cat numeros.txt | sed -n 's/nove/nnn/p ; s/nnn/NNN/p'
$ cat numeros.txt | sed -n 's/nove/nnn/ ; s/nnn/NNN/p'

Para "debug", mostre a linha com o 'p'

$ cat numeros.txt | sed -n 's/nove/nnn/ ; s/nnn/NNN/; 9p'
$ cat numeros.txt | sed -n 's/nove/nnn/ ; 9p; s/nnn/NNN/; 9p'
$ cat numeros.txt | sed -n '9p; s/nove/nnn/ ; 9p; s/nnn/NNN/; 9p'

Dois comandos diferentes, mesmo resultado

$ cat numeros.txt | sed '5,$ d'                # da 5a linha ao final
$ cat numeros.txt | sed -n '1,4 p'             # idem anterior

A ordem dos fatores NÃO altera o resultado

$ cat numeros.txt | sed -n '5p;5p;5p;5p'
$ cat numeros.txt | sed '9d ; 7d ; 5d'
$ cat numeros.txt | sed -n '9p ; 7p ; 5p'

Endereçando pelo texto da linha

$ cat numeros.txt | sed -n '/quatro/ p'
$ cat numeros.txt | sed -n '/o/ p'             # emulando o grep!
$ cat numeros.txt | grep o
$ l -l /bin/sed /bin/grep                      # quem é menor?
$ l -l /bin/sed /bin/gawk                      # e agora? :)
$ cat numeros.txt | sed -n '/o/ p'
$ cat numeros.txt | sed -n '/quatro/ p'
$ cat numeros.txt | sed -n '/quatro/ d'
$ cat numeros.txt | sed '/quatro/ d'
$ cat numeros.txt | sed '/:/ d'

Negando um endereço

$ cat numeros.txt | sed '/o/ d'                # apaga as linhas 'o'
$ cat numeros.txt | sed '/o/! d'               # não apaga as linhas 'o'

Misturando os tipos de endereço

$ cat numeros.txt | sed '/dois/,/cinco/ d'
$ cat numeros.txt | sed '/dois/,6 d'
$ cat numeros.txt | sed '8,6 d'
$ cat numeros.txt | sed 'd'                    # sem endereço == todas
$ cat numeros.txt | sed '1,/dois/ d'
$ cat numeros.txt | sed '/dois/,$ d'
$ cat numeros.txt | sed '/4/,$ d'              # o $ é a última linha

Endereço especial: ~ (step)

$ cat numeros.txt | sed '4~2 d'                # não funciona no UNIX!
$ cat numeros.txt | sed '1~2 d'                # linhas ímpares
$ cat numeros.txt | sed '2~2 d'                # linhas pares
$ cat numeros.txt | sed '1~5 d'                # pule de 5 em 5

\n, \t e amigos

$ sed -V                                       # dependendo da versão
$ echo aaa | sed 's/a/a\n/g'                   # aceita \n, \t, ...
$ echo aaa | sed 's/a/a\t/g'
$ echo aaa | sed 's/a/a       /g'              # quando não, Ctrl+V TAB
$ cat /etc/passwd
$ cat /etc/passwd | sed -n '/edival/ p'
$ cat /etc/passwd | sed -n '/edival/ p' | sed 's/:/\n/g'

O comando q (quit)

$ cat numeros.txt | sed q
$ cat numeros.txt | head -n 4                  # as 4 primeiras
$ cat numeros.txt | sed 4q                     # idem anterior

Aplicando o s/// num intervalo de linhas

$ cat numeros.txt | sed 's/e/___/g'
$ cat numeros.txt | sed '4,6 s/e/___/g'
$ cat numeros.txt | sed '/dois/,/cinco/ s/e/___/g'
$ cat numeros.txt | sed '/dois/,/cinco/s/e/___/g'

Use qualquer caractere como delimitador do s///

$ echo a/a/a | sed 's/a/A/g'
$ echo a/a/a | sed 's///@/g'                   # erro
$ echo a/a/a | sed 's/\//@/g'                  # ok
$ echo a/a/a | sed 's^/^@^g'                   # ^
$ echo a/a/a | sed 's4/4@4g'                   # 4
$ echo a/a/a | sed 's / @ g'                   # espaço
$ echo a/a/a | sed 'ss/s@sg'                   # s
$ echo a/a/a | sed 's /       @       g'       # TAB
$ echo a/a/a | sed 's§/§@§g'                   # §
$ echo a/a/a | sed 's¢/¢@¢g'                   # ¢
$ echo a/a/a | sed 's
> /
> @
> g                                            # Enter

Use qualquer caractere como delimitador do endereço

$ echo a/a/a | sed '/\// s^/^@^g'
$ echo a/a/a | sed '\@/@ s^/^@^g'              # \@end@
$ echo a/a/a | sed '\ /  s / @ g'              # espaço

Agrupando vários comandos em bloco num mesmo endereço

$ cat numeros.txt | sed '5s/:/_/g ; 5s/i/X/g'
$ cat numeros.txt | sed '5 { s/:/_/g ; s/i/X/g ; }'

Os modificadores do comando s///

$ echo aaaaa | sed 's/a/X/'                    # nenhum
$ echo aaaaa | sed 's/a/X/g'                   # g - global
$ echo aaaaa | sed -n 's/a/X/gp'               # p - print
$ echo aaaaa | sed 's/a/X/3'                   # número
$ echo aaaaa | sed 's/a/X/9'
$ echo aaaaa | sed 's/a/X/w foo'               # w - write
$ cat foo

O comando l - list

$ echo -e "\aXX\tXX"
$ echo -e "\aXX\tXX" | sed -n l                # mostra invisíveis
$ echo -e "\aXX\tXX " | sed -n l               # $ indica fim da linha
$ echo -e "\aXX\tXX " | od -c                  # comando similar

O comando n - next

$ cat numeros.txt | sed 'n'                    # faz nada
$ cat numeros.txt | sed 'n;d'                  # apaga linhas pares
$ cat numeros.txt | sed '1d;n;d'               # apaga linhas ímpares
$ cat numeros.txt | grep -A1 oito
$ cat numeros.txt | sed -n '/oito/{p;n;p;}'    # similar do grep -A1

O comando N - next

$ cat numeros.txt | sed -n '/um/{N;l;}'        # junta e separa com \n
$ cat numeros.txt | sed -n '/um/{N; s/\n/@/p ;}'
$ cat numeros.txt | sed -n '/um/{N;N;N;s/\n/@/gp ;}'
$ cat numeros.txt | sed -n 'N;N;s/\n/@@@/gp'   # junta de 3 em 3

Os comandos de pulo : b t - label & branch

$ cat numeros.txt | sed ':a'                   # marca a posição 'a'
$ cat numeros.txt | sed :                      # comando nulo == cat
$ cat numeros.txt | sed ':a ; N ; b a'         # loop, erro no UNIX
$ cat numeros.txt | sed ':a ; N ; s/\n/@@@/; b a'
$ cat numeros.txt | sed ':a ; $!N ; s/\n/@@@/; t a' # tudo numa linha
$ cat numeros.txt | sed ':a ; $!N ; s/\n/@@@/; t a ; s/@@@/###/g'
$ cat numeros.txt | tr -d '\n'
$ cat numeros.txt | tr '\n' @

Os comandos de buffer h g x - hold, get e eXchange

$ cat numeros.txt | sed '1h;$g'                # troque a última pela 1a
$ cat numeros.txt | sed '1h;$G'                # repete a 1a no final
$ cat numeros.txt | sed '1s/:/@/;1h;$G'
$ cat numeros.txt | sed '1{h;s/:/@/g;x;} ; $G'

Um arquivo só de comandos SED

$ echo '1{h;s/:/@/g;x;} ; $G' > script.sed
$ vi script.sed                                # pode alinhar, comentar
$ cat numeros.txt | sed -f script.sed          # usar o -f
$ sed -f script.sed numeros.txt                # com ou sem o cat
$ chmod +x script.sed                          # pode ser executável
$ ./script.sed numeros.txt                     # coloque #!/bin/sed -f
$ mv script.sed meuprog                        # extensão é opcional
$ ./meuprog numeros.txt

O comando =

$ cat numeros.txt | sed =                      # mostra número da linha
$ cat numeros.txt | sed 5=
$ cat numeros.txt | wc  -l
$ cat numeros.txt | sed -n '$='                # idem anterior

Expressões Regulares

As Expressões Regulares são uma simbologia, um método de se descrever padrões de texto complicados ou posicionais, como "números no final da linha" ou "palavras repetidas na mesma linha".

As âncoras ^ e $ para começo e fim de linha

$ cat numeros.txt | grep e                     # procure e
$ cat numeros.txt | grep 'e$'                  # procure e no fim
$ cat numeros.txt | grep '^1'                  # procure 1 no início
$ cat numeros.txt | grep '^$'                  # linhas em branco

O ou de caracteres [] (lista)

$ cat numeros.txt | grep '[eo]$'               # e ou o no fim
$ cat numeros.txt | grep '[aeiou]$'            # vogais no fim
$ cat numeros.txt | grep '^[aeiou]'            # vogais no início
$ cat numeros.txt | cut -d: -f2-               # só as palavras
$ cat numeros.txt | cut -d: -f2- | grep '^[aeiou]'
$ cat numeros.txt | cut -d: -f2- | grep '^[aeiouuifduieuifg]'
$ cat numeros.txt | cut -d: -f2- | grep '^[aaaa]'
$ cat numeros.txt | cut -d: -f2- | grep '^[a-z]' # intervalo
$ cat numeros.txt | cut -d: -f2- | grep '^[#-@]'
$ cat numeros.txt | grep '^[a-z]'              # letras no início
$ cat numeros.txt | grep '^[0-9]'              # números no início
$ cat numeros.txt | grep '^[0-9][0-9]'         # dois números no início
$ cat numeros.txt | grep '^[0-9]:[aeiou]'      # número:vogal
$ cat numeros.txt | grep '^[0-9]:[^aeiou]'     # número:não vogal
$ cat numeros.txt | grep '^[0-9]:[aeiou^-]'    # número:vogal ou ^ ou -

O repetidor {} (chaves)

$ cat numeros.txt | egrep '^[0-9]{1,}:'        # um ou mais números
$ cat numeros.txt | egrep '^[0-9]{1,5}:'       # de 1 a 5 números
$ cat numeros.txt | egrep '^[0-9]{2}:'         # exatamente 2 números
$ cat numeros.txt | grep '^[0-9]\{2\}:'        # _e_grep não escapa

O curinga . (ponto)

$ cat numeros.txt | grep '^[0-9]:.[aeiou]'     # núm : qqr vogal
$ cat numeros.txt | grep '^[0-9]:..[aeiou]'    # núm : qqr qqr vogal
$ cat numeros.txt | grep '^[0-9]:...[aeiou]'   # núm : qqr qqr qqr vogal
$ cat numeros.txt | grep '\.'                  # ponto literal
$ cat numeros.txt | grep '^.........[aeiou]'   # vários pontos
$ cat numeros.txt | egrep '^.{9}[aeiou]'       # 10a é vogal
$ cat numeros.txt | egrep '^.{7,9}[aeiou]'     # 8a, 9a ou 10a é vogal
$ cat numeros.txt | egrep '^.{9,}[aeiou]'      # 10a em diante é vogal
$ cat numeros.txt | egrep '^.{10}$'            # linha com 10 letras
$ cat numeros.txt | egrep '^.{1,10}$'          # linha com até 10 letras
$ cat numeros.txt | egrep '^.{10,}$''          # linha com mín 10 letras
$ cat numeros.txt | egrep '[aeiou]..$'         # antepenúltima é vogal

Os atalhos ? * +

$       ? == {0,1}       * == {0,}       + == {1,}
$ cat numeros.txt | egrep 'X'                  # X uma vez
$ cat numeros.txt | egrep 'X*'                 # X zero ou mais vezes
$ cat numeros.txt | egrep 'X+'                 # X uma ou mais vezes
$ cat numeros.txt | egrep 'X?'                 # X opcional (0 ou 1)
$ echo aulas | egrep 'aulas?$'                 # aula ou aulas no fim
$ echo aula | egrep 'aulas?$'

O agrupador ()

$ echo aulas | egrep '(aulas)?$'               # aulas no fim ou não

ER para casar endereço IP (N.N.N.N)

$ cat ip                                       # ER pra casar IP
10.0.0.1
192.168.255.255
$ cat ip | egrep '^([0-9]{1,3}\.){4}$'         # não funciona
$ cat ip | egrep '^([0-9]{1,3}\.){3}[0-9]{3}$' # quase
$ cat ip | egrep '^([0-9]{1,3}\.){3}[0-9]{1,3}$' # ok!

ER de zero a 255

$ seq 260                                      # olhe a evolução
$ seq 260 | egrep -v '^[0-9]$'
$ seq 260 | egrep -v '^[0-9]{2}$'
$ seq 260 | egrep -v '^[0-9]{1,2}$'
$ seq 260 | egrep -v '^([0-9]{1,2}|1[0-9][0-9])$'
$ seq 260 | egrep -v '^([0-9]{1,2}|1[0-9][0-9]|2[0-5][0-5])$'
$ seq 260 | egrep -v '^([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9])$'
$ seq 260 | egrep -v '^([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$'

ER do IP mais precisa (N.N.N.N onde N de 0 a 255)

$ cat ip | egrep '^(([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$'
$ ##### usando variáveis fica mais claro
$ de0a255='([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
$ cat ip | egrep "^($de0a255\.){3}$de0a255$'

O ou de palavras |

$ echo isso | sed 's/isso|aquilo/XXX/'         # isso ou aquilo
$ echo aquilo| sed 's/isso|aquilo/XXX/'
$ echo aquilo| sed 's/\(iss\|aquil\)o/XXX/'    # isso ou aquilo
$ echo aquilo| sed -r 's/(iss|aquil)o/XXX/'    # com -r, não escapa

O curinga .*, o tudo e o nada

$ echo a:a:a:a:a:a | sed 's/a:.*:/X/'
$ echo "<b>abc</b> foo bar <b>xyz</b> XXX" | sed 's,<b>.*</b>,,'

Os retrovisores \1, \2 ... \9

$ echo abcdefg | sed -r 's/(a)(b)(c)/\3:\2:\1/'
$ echo abcdefg | sed -r 's/(a)(b)(c).*/\1:\2:\3/'
$ echo abcdefg | sed -r 's/(a)(b)(c).*/\3:\2:\1/'
$ cat numeros.txt | sed -r 's/^(.*):(.*):(.*)/\3 \2 \1/'

ER para casar números repetidos 11, 22, 33, ...

$ seq 99 | egrep '[0-9]{2}'                    # não é isso
$ seq 99 | egrep '([0-9])\1'                   # ok!

ER para casar palavras repetidas

$ echo quero quero | egrep '([a-z]+) \1'       # casou
$ echo quero aula | egrep '([a-z]+) \1'        # não casa
$ echo quero aula agora, eu quero | egrep '([a-z]{3,}) .*\1'
— EOF —

Gostou desse texto? Aqui tem mais.