Selecione linhas do arquivo de texto que possuem ids listados em outro arquivo

11

Eu uso muito grep awk sort no meu shell unix para trabalhar com arquivos de texto de coluna separados por tabulação de tamanho médio (em torno de 10M-100M linhas). A esse respeito, o shell unix é minha planilha.

Mas tenho um grande problema, isto é, selecionar registros com uma lista de IDs.

Tendo o arquivo table.csv com formato id\tfoo\tbar... e ids.csv arquivo com lista de IDs, selecione somente registros de table.csv com ID presente em ids.csv .

tipo de link mas com shell, não perl.

grep -F obviamente produz falsos positivos se os ids forem de largura variável. join é um utilitário que eu nunca consegui descobrir. Primeiro de tudo, requer classificação alfabética (meus arquivos são geralmente numericamente ordenados), mas mesmo assim eu não consigo fazer isso funcionar sem reclamar sobre pedidos incorretos e pular alguns registros. Então eu não gosto disso. grep -f contra o arquivo com ^id\t -s é muito lento quando o número de ids é grande. awk é complicado.

Existe alguma boa solução para isso? Alguma ferramenta específica para arquivos separados por tabulações? Funcionalidades extras também serão muito bem-vindas.

UPD: corrigido sort - > join

    
por alamar 23.01.2014 / 20:39

4 respostas

18

Eu acho que você quis dizer grep -f não grep -F , mas você realmente precisa de uma combinação de ambos e -w :

grep -Fwf ids.csv table.csv

A razão pela qual você estava obtendo falsos positivos é (eu acho que você não explicou) porque se um id puder ser contido em outro, ambos serão impressos. -w remove esse problema e -F garante que seus padrões sejam tratados como sequências, não como expressões regulares. De man grep :

   -F, --fixed-strings
          Interpret PATTERN as a  list  of  fixed  strings,  separated  by
          newlines,  any  of  which is to be matched.  (-F is specified by
          POSIX.)
   -w, --word-regexp
          Select  only  those  lines  containing  matches  that form whole
          words.  The test is that the matching substring must  either  be
          at  the  beginning  of  the  line,  or  preceded  by  a non-word
          constituent character.  Similarly, it must be either at the  end
          of  the  line  or  followed by a non-word constituent character.
          Word-constituent  characters  are  letters,  digits,   and   the
          underscore.

   -f FILE, --file=FILE
          Obtain  patterns  from  FILE,  one  per  line.   The  empty file
          contains zero patterns, and therefore matches nothing.   (-f  is
          specified by POSIX.)

Se seus falsos positivos forem porque um ID pode estar presente em um campo que não seja de ID, faça um loop no seu arquivo:

while read pat; do grep -w "^$pat" table.csv; done < ids.csv

ou mais rápido:

xargs -I {} grep "^{}" table.csv < ids.csv

Pessoalmente, eu faria isso em perl :

perl -lane 'BEGIN{open(A,"ids.csv"); while(<A>){chomp; $k{$_}++}} 
            print $_ if defined($k{$F[0]}); ' table.csv
    
por 23.01.2014 / 21:11
7

O utilitário join é o que você deseja. Isso requer que os arquivos de entrada sejam classificados em termos lexicais.

Assumindo que seu shell é bash ou ksh:

join -t $'\t' <(sort ids.csv) <(sort table.csv)

Sem precisar classificar, a solução usual do awk é

awk -F '\t' 'NR==FNR {id[$1]; next} $1 in id' ids.csv table.csv
    
por 23.01.2014 / 21:24
2

As respostas para esta pergunta importante ajudou-me a contornar os problemas com a adesão. Essencialmente, quando você classifica o arquivo em preparação para enviá-lo para participar, é necessário verificar se está classificando com base na coluna na qual está entrando. Então, se esse é o primeiro, você precisa dizer qual é o caractere separador no arquivo e o classificar no primeiro (e apenas no primeiro campo). Caso contrário, se o primeiro campo tiver larguras variáveis (por exemplo), seus separadores e possivelmente outros campos poderão começar a afetar a ordem de classificação.

Portanto, use a opção -t de classificação para especificar seu caractere de separação e use a opção -k para especificar o campo (lembrando que você precisa de um campo de início e fim - mesmo que seja o mesmo) ou classificar daquele personagem até o final da linha).

Então, para um arquivo separado por tabulação, como nesta pergunta, o seguinte deve funcionar (com agradecimentos a resposta de glenn para estrutura):

join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv

(Para referência, o sinalizador -d significa a classificação do dicionário. Você também pode querer usar o sinalizador -b para ignorar o espaço em branco inicial, consulte man sort e man join ).

Como um exemplo mais geral, suponha que você esteja participando de dois arquivos separados por vírgulas - input1.csv na terceira coluna e input2.csv na quarta. Você poderia usar

join -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv

Aqui, as opções -1 e -2 especificam quais campos serão unidos no primeiro e no segundo arquivos de entrada, respectivamente.

    
por 08.06.2017 / 11:02
0

Você também pode usar o ruby para fazer algo semelhante:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
    
por 24.01.2014 / 18:42

Tags