Procura de arquivos de texto por coluna

4

Eu tenho um arquivo neste formato:

[#]   OWNER_NAME     NAME                       SIZE
[6]   Robottinosino  Software                   200
[42]  Robottinosino  Ideas worth zero           188
[12]  Robottinosino  Ideas worth zero or more   111
[13]  I am Batman    Hardware                   180
[25]  Robottinosino  Profile Pictures           170

e gostaria de poder fazer o seguinte usando ferramentas de linha de comando:

my_command "Ideas worth zero"

e obtenha este resultado:

42

e não corre o risco de obter este resultado:

12

Eu pensei em usar o grep para identificar a linha, awk para obter o primeiro campo, mas não tenho certeza de como corresponder de forma confiável e eficiente em todo o campo 'NAME', contando em que coluna o texto 'OWNER_NAME' e 'SIZE' aparece no cabeçalho e obtém tudo no meio com algum recorte de espaços em branco.

O aviso "OWNER_NAME" pode ter mais de uma palavra: por exemplo, 'OWNER_NAME'="Eu sou o Batman".

Alguma ideia de acompanhamento da implementação?

O que eu tenho que passar por aqui, é apenas a antiga família de gato, cabeça, cauda, awk, sed, grep, cut, etc.

    
por Robottinosino 25.06.2012 / 09:22

6 respostas

0

Não é como se eu não tivesse tentado antes de perguntar ... aqui está minha tentativa ... mas parece muito complicado para mim. Desconsidere a lógica que lida com os arquivos sujos de forma graciosa, isso não fazia parte da questão e não é o foco da pesquisa de texto de qualquer maneira. Acontece que os arquivos que eu tenho às vezes não começam com "HEADER", mas com algum lixo, com todo o resto dos dados sendo absolutamente bons, sempre.

#!/bin/bash

file_to_scan="${1}"
name_to_lookup="${2}"

ASSUME_FIRST_LINE_IS_HEADER="false" # Sometimes input files begin with spurious lines

FILE_HEADER_REGEX='^\[#\][[:blank:]]+OWNER_NAME[[:blank:]]+NAME[[:blank:]]+SIZE\s*$'

FIELD_HEADER_NAME=' NAME'
FIELD_HEADER_SIZE=' SIZE'

if [ "$ASSUME_FIRST_LINE_IS_HEADER" == "true" ]; then
    header_line=$(head -n 1 "${file_to_scan}")
else
    header_line="$(
        grep \
            --colour=never \
            --extended-regexp \
            "${FILE_HEADER_REGEX}" \
            "${file_to_scan}"
        )"
fi

colstartend=($(
    printf "${header_line}" \
        | \
        awk \
            -v name="${FIELD_HEADER_NAME}" \
            -v size="${FIELD_HEADER_SIZE}" \
            '{
                 print index($0, name)+1;
                 print index($0, size);
             }'
))

sed -E "1,/${FILE_HEADER_REGEX}/d" "${file_to_scan}" \
    | \
    awk \
        -v name_to_lookup="${name_to_lookup}" \
        -v colstart="${colstartend[0]}" \
        -v offset="$(( ${colstartend[1]} - ${colstartend[0]} ))" \
        '{
             name_field = substr($0, colstart, offset);
             sub(/ *$/, "", name_field);
             if (name_field == name_to_lookup) {
               print substr($1, 2, length($1)-2)
             }
         }'
    
por 25.06.2012 / 12:43
3

OK, se o comprimento das colunas não for conhecido, mudaria para uma linguagem mais poderosa que o bash:

#!/usr/bin/perl
use warnings;
use strict;

my $string = shift;
open my $FH, '<', '1.txt' or die $!;
my $first_line = <$FH>;
my ($before, $name) = $first_line =~ /(.* )(NAME *)/;
my $column = length $before;
$string .= ' ' x (length($name) - length $string);     # adjust the length of $string
while (<$FH>) {
    if ($column == index $_, $string, $column) {
        /^\[([0-9]+)\]/ and print "$1\n";
    }
}
    
por 25.06.2012 / 10:32
3

Se as larguras de campo forem constantes - ou seja, o formato de arquivo que você exibiu com as larguras de campo está no máximo - você pode usar o GNU awk ( gawk(1) ) e definir a variável FIELDWIDTHS para usar largura fixa análise:

gawk -v searchstr="Ideas worth zero" -- '
    BEGIN { FIELDWIDTHS="6 15 27 5" }  # assuming the final field width is 5
    # Pre-process data
    {
        gsub(/[^[:digit:]]/, "", $1)  # strip out non-numbers
        for (i = 2; i <= NF; i++)
            gsub(/[[:space:]]*$/, "", $i)  # strip trailing whitespace
    }
    # match here
    $3 == searchstr { print $1 }
' file.txt

Você pode incluir isso em um shell script ou em uma função e parametrizar searchstr ( -v searchstr="$1" ).

No entanto, se os campos forem de largura variável - ou seja, se os dados forem alterados, a largura dos campos pode mudar - você precisará ser um pouco mais inteligente e determinar dinamicamente as larguras de campo da inspeção da primeira linha. Dado que um campo é chamado de OWNER_NAME , usando um sublinhado, estou assumindo que os espaços não estão presentes nos nomes dos campos, portanto, posso supor que o espaço em branco separe os nomes dos campos.

Com isso definido, você pode substituir a linha BEGIN... por esse código:

NR == 1 {
    for (i = 2; i <= NF; i++)
        FIELDWIDTHS=FIELDWIDTHS index($0" ", " "$i" ")-index($0" ", " "$(i-1)" ") " "
    FIELDWIDTHS=FIELDWIDTHS "5"  # assuming 5 is the width of the last field
    next
}

Isso examinará os campos na primeira linha e calculará as larguras dos campos calculando a diferença entre as posições dos campos subseqüentes do segundo ao último campo. Eu assumi que a largura do último campo é 5, mas eu acho que você pode simplesmente colocar um grande número lá e isso vai funcionar com o que sobrou.

Precisamos procurar um espaço antes e depois do nome para garantir que não encontremos NAME dentro de OWNER_NAME (ou se houvesse um campo chamado OWNER ) e, em vez disso, correspondamos a todo o campo (também precisa adicionar um espaço para $0 para garantir que possamos combinar um espaço no final, mesmo que não haja nenhum lá.

Você pode ficar mais interessado para poder consultar o nome do campo, em vez de corresponder apenas a $3 , mas deixarei isso para você.

    
por 25.06.2012 / 10:39
1
 $ cat test
[#]     OWNER_NAME      NAME    SIZE
[6]     Robottinosino   Software        200
[42]    Robottinosino   Ideas worth zero        188
[12]    Robottinosino   Ideas worth zero or more        111
[13]    I am Batman     Hardware        180
[25]    Robottinosino   Profile Pictures        170

 $ cat test.sh
#!/bin/bash -
awk -F"\t" '(NR<=1){for(i=1;i<NF;i++) if(toupper("'$1'")==toupper($i)) field=i;} (toupper($field) == toupper("'"$2"'")){print $1}'

 $ cat test | ./test.sh NAME "Ideas worth zero"
[42]

Não tenho certeza se o delimitador é uma guia. Mas é muito fácil alterá-lo com sed . Por exemplo, sed 's/\s\s+/\t/g' fará o trabalho.

Além disso, você pode especificar qualquer outro campo, não apenas NAME . Ele encontrará o próprio número da coluna da direita.

No caso de precisar apenas de um script de terceira coluna, será muito mais fácil.

ps. Eu usei isso no meu próprio projeto, portanto, parece que tem uma funcionalidade muito maior do que você precisa.

upd. devido ao delimitador não é uma linha de lançamento de alteração de tabulação para

 cat test | sed 's/\s\s\+/\t/g' | ./test.sh NAME "Ideas worth zero"

Funciona perfeitamente no meu site.

    
por 25.06.2012 / 09:59
0

Provavelmente, o mais simples é filtrar as linhas primeiro por 'Idéias que valem zero, depois lançando as linhas' ... ou mais ':

grep 'Ideas worth zero' | grep -v 'Ideas worth zero or more'

E para obter o número desse pipe na entrada:

cut -d' ' -f1 | tr -d ']['

Que corta o primeiro campo (delimitado por um espaço) e remove os parênteses de arco.

Melhor seria se você pudesse alterar um pouco o formato do arquivo de forma que ele venha com delimitadores de campo adequados.

    
por 25.06.2012 / 09:53
0

Isso pode ajudar você:

function my_command () {
    sed -n $(cut -b22-48 1.txt |
        grep -n "$1"' *$' |
        cut -f1 -d: )p 1.txt \
            | cut -d' ' -f1 | tr -d ']['
}

Ele corta somente a coluna relevante da entrada, procura o número da linha onde a string aparece, depois pega essa linha e mantém apenas o número na primeira coluna dela.

    
por 25.06.2012 / 09:56