Curso
Existem muitos cenários em que você precisa analisar, modificar e processar rapidamente arquivos grandes, tanto em quantidade quanto em tamanho. Esses arquivos costumam ser CSV (Comma Separated Values) em texto puro. Abrir esse tipo de arquivo em planilhas como Excel, LibreOffice ou OpenOffice pode estourar a memória da sua máquina. Além disso, processar muitos arquivos em lote costuma falhar depois de algumas horas por conta de alguma anomalia inesperada.
Você inevitavelmente perde muito tempo com telas travadas, reinicializações e longas esperas.
Só que a maioria dessas tarefas poderia ser feita com poucas linhas de código. Mais ainda: estar familiarizado com alguns comandos simples de shell pode economizar muito tempo e reduzir a frustração.
Este post traz uma visão geral de alguns comandos de shell que eu uso praticamente todos os dias. Espero que sejam úteis para você.
Anotações rápidas
Atenção: existem diferentes tipos de shell (bash, zsh, ...). O bash é o mais comum, pois é o shell padrão no OS X e nas principais distribuições Linux. O Zsh (e o Oh My Zsh) é uma alternativa popular e poderosa. Os comandos de shell deste post foram testados no bash no OS X (macOS) e devem funcionar em outros shells e ambientes.
Todos os exemplos a seguir usam o conjunto de dados adult do repositório UCI Machine Learning, também conhecido como "Census Income", disponível aqui. Esse dataset é comumente usado para prever se a renda excede \$50K/ano com base em dados do censo. Com 48.842 linhas e 14 atributos, não é um dataset grande, mas é suficiente para ilustrar os exemplos. Embora o arquivo tenha a extensão .data, ele é um CSV bem formatado. Fique à vontade para baixar e acompanhar!
Conte com wc
Dado um novo arquivo de texto, você quer saber quantas linhas ele tem. Isso pode ser feito com o comando de contagem de palavras wc -l:
$ wc -l adult.data
32562 adult.data
O que indica que o arquivo adult.data contém 32.562 linhas. A flag -l instrui o wc a contar linhas. Você também pode usar wc para contar palavras com a flag -w.
$ wc -w adult.data
488415 adult.data
O arquivo adult.data contém quase 500 mil palavras.
O comando wc também pode contar quantos arquivos há em um diretório usando a saída de um simples ls -l como entrada para o wc. Usar a saída de um comando como entrada para outro com o pipe | é um padrão útil chamado pipelining. Você vai vê-lo em vários exemplos neste post.
Suponha agora que você tenha uma pasta com muitos arquivos. O comando abaixo informa exatamente quantos itens ela contém:
$ ls -l <folder> | wc -l
508
Há muitas outras aplicações para o wc quando você começa a usar curingas e subpastas.
Voltando ao arquivo adult.data, vamos olhar as primeiras linhas com o comando head. Por padrão, head exibe 10 linhas; você pode limitar a saída às 2 primeiras com a flag -n.
$ head -n 2 adult.data
39, State-gov, 77516, Bachelors, 13, Never-married, Adm-clerical, Not-in-family, White, Male, 2174, 0, 40, United-States, <=50K
50, Self-emp-not-inc, 83311, Bachelors, 13, Married-civ-spouse, Exec-managerial, Husband, White, Male, 0, 0, 13, United-States, <=50K
Note que o arquivo não tem linha de cabeçalho. Os nomes das colunas não estão no arquivo. Você pode adicionar essa linha de cabeçalho concatenando dois arquivos com o comando cat.
Concatene arquivos com cat
O comando cat imprime o conteúdo de um arquivo na saída padrão (ou seja, no terminal). Ele também pode concatenar uma série de arquivos. O comando cat file_1.csv file_2.csv > target_file.csv junta o conteúdo de file_1.csv e file_2.csv em target_file.csv, adicionando file_2.csv ao final de file_1.csv.
O arquivo de cabeçalho não está no dataset original, então você precisa criá-lo. Para isso, use echo para gravar a lista de nomes de colunas separados por vírgula em um arquivo header.csv.
$ echo "age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class" > header.csv
Neste exemplo, você usou o redirecionamento > do shell para despejar a saída do cat no arquivo adult.csv. O > cria um arquivo ou substitui todo o seu conteúdo se ele já existir. Ao dobrar o símbolo, >>, o novo conteúdo é acrescentado ao arquivo existente sem apagar o que já havia.
Agora vamos adicionar o header.csv no início de adult.data. Ao mesmo tempo, renomeie adult.data para adult.csv, já que, no fim das contas, é um arquivo CSV.
$ cat header.csv adult.data > adult.csv
Confira se a primeira linha de adult.csv contém os nomes das colunas com:
$ head -n 1 adult.csv
age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class
39, State-gov, 77516, Bachelors, 13, Never-married, Adm-clerical, Not-in-family, White, Male, 2174, 0, 40, United-States, <=50K
Modifique um arquivo com sed
Outro cenário frequente em data mining é quando um arquivo está corrompido ou mal formatado, por exemplo com caracteres fora de UTF-8 ou uma vírgula fora do lugar. Você pode corrigir o arquivo sem abri-lo usando o comando sed.
O padrão genérico do sed é
$ sed "s/<string to replace>/<string to replace it with>/g" <source_file> > <target_file>.
O dataset adult.csv usa o caractere ? para indicar valor ausente. Você pode substituir esse caractere por um valor padrão mais adequado usando sed. A string vazia é preferível como indicação de valor ausente, pois será interpretada como NaN ao carregar os dados em um DataFrame do Pandas.
$ grep ", ?," adult.csv | wc -l
2399
Isso retorna a contagem de 2.399 linhas com pelo menos uma coluna com valor ausente indicado por ?. O comando a seguir vai substituir todas as colunas com ? por string vazia. Todas as células que contêm apenas ? seguido de um espaço agora ficarão realmente vazias.
$ sed "s/, ?,/,,/g" adult.csv > adult.csv
Note que usamos o delimitador de coluna , nas strings de origem e destino para evitar substituir pontos de interrogação legítimos que possam existir em outras partes do dataset.
Crie um subconjunto de um arquivo grande
Imagine agora que você está lidando com um arquivo com mais de 30 milhões de linhas. Enquanto desenvolve seu script em Python ou R, você vai querer testar seu fluxo de trabalho apenas em uma amostra do arquivo original, sem carregar tudo na memória. Você pode usar uma combinação de head e tail para criar um subamostra do dataset original.
headmostra o início dos arquivos;tailmostra o final dos arquivos
Como vimos, esses comandos aceitam a flag -n (de --lines) para limitar o número de linhas na saída. tail também aceita a flag -f (de --follow) para exibir dados à medida que são acrescentados ao fim do arquivo. Isso é especialmente útil para monitorar arquivos de log enquanto seus scripts estão rodando, com tail -f <logfile>.
Misturando o pipe | com head e tail, você pode extrair um certo número de linhas de dentro de um arquivo e exportar o conteúdo para um arquivo de subconjunto. Por exemplo, para extrair 20 linhas a partir da linha 100:
$ head -n 120 adult.csv | tail -n 20 > adult_sample.csv
Repare que primeiro você pega primeira_linha + numero_de_linhas com head e depois usa tail para obter o numero_de_linhas. O comando genérico é:
$ head -n <total_lines> <source_file> | tail -n <number_of_lines> > <target_file>
onde total_lines = first_line + number_of_lines. Entretanto, seu novo arquivo amostrado não contém mais o cabeçalho. Já sabemos como recolocar o cabeçalho no arquivo de subconjunto usando cat para concatenar o arquivo e o header.csv criado antes:
$ cat adult_sample.csv header.csv > adult_sample_with_header.csv
Note que você não usou o nome de origem adult\_sample.csv como arquivo de destino; em vez disso, criou um novo arquivo chamado adult\_sample\_with\_header.csv. Usar o mesmo nome de um dos arquivos de origem como destino ao rodar cat pode gerar conteúdo inesperado. É melhor criar um novo arquivo como saída do cat.
Encontrando duplicatas com uniq
Com o comando uniq você encontra linhas repetidas adjacentes em um arquivo. O uniq tem várias flags; as mais úteis são:
uniq -c: adiciona a contagem de repetições a cada linha;uniq -d: mostra apenas as linhas duplicadas; euniq -u: mostra apenas as linhas únicas.
Porém, uniq não é "inteligente": linhas repetidas não serão detectadas se não estiverem adjacentes. Ou seja, antes você precisa sortear o arquivo. Este comando conta o número de linhas duplicadas em adult.csv:
$ sort adult.csv | uniq -d | wc -l
23
e mostra que há 23 duplicatas. O próximo comando pega todas as linhas com contagem de repetição, ordena em ordem reversa e exibe as 3 duplicatas mais frequentes:
$ sort adult.csv | uniq -c | sort -r | head -n 3
3 25, Private, 195994, 1st-4th, 2, Never-married, ...
2 90, Private, 52386, Some-college, 10, Never-married, ...
2 49, Self-emp-not-inc, 43479, Some-college, 10, Married-civ-spouse, ...
Há muitas combinações poderosas ao juntar sort e uniq com diferentes flags. Use man sort e man uniq para explorar mais.
Selecionando colunas com cut
O bom de arquivos CSV e comandos de shell é que você também pode trabalhar no nível de colunas usando cut para selecionar uma coluna específica. cut recebe duas flags principais: -d para especificar o delimitador de coluna e -f para especificar as colunas desejadas. No exemplo a seguir, usamos cut para descobrir quantos valores únicos a variável categórica workclass (coluna 2) assume.
Primeiro selecione a coluna workclass e faça um pipe para head para verificar se é a coluna certa:
$ cut -d "," -f 2 adult.csv | head -3
workclass
State-gov
Self-emp-not-inc
Agora, para contar os valores únicos, ordene a saída do cut e envie para uniq -c, assim:
$ cut -d "," -f 2 adult.csv | sort | uniq -c
1837
960 Federal-gov
2093 Local-gov
7 Never-worked
22696 Private
1116 Self-emp-inc
2541 Self-emp-not-inc
1298 State-gov
14 Without-pay
1 workclass
O que indica, por exemplo, que há 1.837 valores nulos e que a classe principal, de longe, é Private com 22.969 ocorrências.
A linha de comando acima é semelhante ao método value_counts() aplicado a uma Series do DataFrame com os dados do adult.csv.
Laços (looping)
Até aqui, trabalhamos principalmente com um arquivo. Para renomear, processar ou transferir grandes quantidades de arquivos, vale acrescentar laços ao seu kit de shell. O formato genérico de um loop no bash é:
while true; do
_do something_ ;
done
Vamos colocar isso em prática. Imagine que você tem 1000 arquivos com espaços no nome e quer substituir cada espaço por um sublinhado "_".
replace_source=' '
replace_target='_'
for filename in ./*.csv; do
new_filename=${filename//$replace_source/$replace_target}
mv "$filename" "$new_filename"
done
Neste exemplo,
- primeiro declaramos duas variáveis:
replace_sourceereplace_targetcomo placeholders para o espaço e o sublinhado - iteramos sobre todos os arquivos
*.csvna pasta atual - para cada
filename, criamos umnew_filenamesubstituindo cada espaço por um sublinhado - e renomeamos o arquivo do nome atual para o novo nome com o comando
mv
Ou seja, além de usar o shell para percorrer arquivos em um diretório, você também criou variáveis.
Variáveis
Vamos encerrar esta introdução a comandos de shell falando de variáveis. Criar variáveis no shell é simples:
$ varname='<a string>'
$ varname=a number
Por exemplo:
$ varname='Hello world'
$ varname=123.4
Note que não há espaço ao redor do sinal de '='. var = '<a string>' não funciona. Para retornar o valor da variável, basta usar echo:
$ echo $varname
123.4
E para usá-la em um script, encapsule-a entre aspas, como vimos:
$ mv "$filename" "$new_filename"
Escrever loops e usar variáveis abre caminho para manipulações de arquivos mais complexas e é a porta de entrada para o shell scripting, que vai além deste artigo introdutório. Mas saiba que shell scripting é simples o suficiente se você começar pequeno e evoluir conforme ganha confiança.
Conclusão
Linhas de comando em shell são extremamente úteis no seu dia a dia como cientista de dados. Há muitos outros exemplos e casos de uso a explorar. E, como você vai perceber, quase sempre existem diferentes maneiras de chegar ao mesmo resultado usando a ampla variedade de comandos e flags disponíveis. Nesses casos, manter a solução simples costuma ser a melhor estratégia.
Tomara que os exemplos apresentados aqui ajudem você a incorporar comandos de shell ao seu kit de processamento de dados. Fale comigo e compartilhe seus truques de shell no Twitter: @alexip.
Dê uma olhada no nosso tutorial Configure um ambiente de ciência de dados no seu computador.

