bash + como verificar todas as palavras em cada linha com a mesma contagem

1

como verificar se todas as palavras / strings em cada linha estão com a mesma contagem

se todas as palavras em cada linha tiverem a mesma contagem, a sintaxe retornará true e o número da palavra contada

se as linhas não tiverem a mesma contagem, a sintaxe retornará false e count = NA

por exemplo, com relação ao exemplo a seguir, obteremos true e count = 5

sdb sde sdc sdf sdd
sdc sdb sde sdd sdf
sdb sdc sde sdf sdd
sde sdb sdd sdc sdf
sdc sde sdd sdb sdf

exemplo com relação ao exemplo a seguir, obteremos false e count = NA

sdb sde sdc sdf sdd
sdc sdb sde sdd sdf
sdb sdc sde sdf 
sde sdb sdd sdc sdf
sde sdd sdb sdf

outro exemplo com relação ao exemplo a seguir, obteremos false e count = NA

sdb sde sdc sdf sdd
sdc sdb sde sdd sdf
sdb sdc sde sdf 
sde sdb sdd sdc sdf
sde sdd sdb sdf sde 
    
por yael 16.01.2018 / 11:27

7 respostas

2

Usando awk :

awk 'BEGIN { r = "true" } NR == 1 { n = NF; next } NF != n { r = "false"; n = "N/A"; exit } END { printf("status=%s count=%s\n", r, n) }' somefilename

Ou como um script awk :

#!/usr/bin/awk -f

BEGIN { r = "true" }

NR == 1 { n = NF; next }
NF != n { r = "false"; n = "N/A"; exit }

END { printf("status=%s count=%s\n", r, n) }

O script começará com r (como em "resultado") para true (estamos assumindo que será verdadeiro em vez de falso). Em seguida, ele inicializa n (como em "número") para o número de campos da primeira linha.

Se qualquer uma das outras linhas nos dados de entrada tiver um número diferente de campos, r será definido como false e n será definido como N/A e o script sairá (por meio do bloco END ).

No final, os valores atuais de r e n são impressos.

A saída deste script será algo como

status=true count=5

ou

status=false count=N/A

Isso pode ser usado com export ou bash declare ou eval :

declare $( awk '...' somefilename )

Isso criaria a variável de shell count e status , e elas estariam disponíveis no shell de chamada.

    
por 16.01.2018 / 11:42
1

Você pode usar uma matriz associativa para manter o número de cada contagem:

#!/bin/bash
declare -A seen
while read -a line ; do
    (( seen[${#line[@]}]++ ))
done

if [[ ${#seen[@]} == 1 ]] ; then
    echo count=${#seen[@]}
    exit
else
    echo count=NA
    exit 1
fi

Ou você pode usar ferramentas externas para fazer o trabalho. Por exemplo, o script a seguir usa Perl para contar o número de palavras (por sua opção -a autosplit), sort -u para obter contagens exclusivas e wc -l para verificar se há uma contagem ou mais.

#!/bin/bash
out=$(perl -lane 'print scalar @F' | sort -u)
if ((1 == $(wc -l <<<"$out") )) ; then
    echo count=$out
    exit
else
    echo count=NA
    exit 1
fi
    
por 16.01.2018 / 11:35
1
if
  count=$(
    awk 'NR == 1 {print count = NF}
         NF != count {exit 1}' < file
  )
then
  if [ -z "$count" ]; then
    echo "OK? Not OK? file is empty"
  else
    echo "OK all lines have $count words"
  fi
else
  echo >&2 "Not all lines have the same number of words or the file can't be read"
fi

Note que na última parte, você pode diferenciar contagem diferente e não pode abrir o arquivo com [ -z "$count" ] novamente.

    
por 16.01.2018 / 12:26
0

Awk solução:

awk 'NR==1{ c=NF; st="true" }
     NR>1 && !(NF in a){ c="NA"; st="false"; exit }{ a[NF] }
     END{ printf "count=%s status=%s\n", c, st }' file
    
por 16.01.2018 / 11:44
0
#!/usr/bin/perl

use strict; # get perl to warn us if we try to use an undeclared variable.

# get all words on first line, and store them in a hash
#
# note: it doesn't matter which line we get the word list from because
# we only want to know if all lines have the same number of identical
# words.
my %words = map { $_ => 1 } split (/\s+/,<>);

while(<>) {
  # now do the same for each subsequent line
  my %thisline = map { $_ => 1 } split ;

  # and compare them.  exit with a non-zero exit code if they differ.
  if (%words != %thisline) {
    # optionally print a warning message to STDERR here.
    exit 1;
  }
};

# print the number of words we saw on the first line
print scalar keys %words, "\n";
exit 0

(o exit 0 na última linha não é necessário - esse é o padrão de qualquer forma. É "útil" apenas documentar que o código de retorno é uma parte importante da saída deste programa.

OBSERVAÇÃO : isso não contará palavras duplicadas em uma linha. por exemplo. sda sdb sdc sdc sdc contará como 3 palavras, não 5 porque as últimas três palavras são as mesmas. Se isso for significativo, os hashes também devem contar o número de vezes que cada palavra aparece. Algo parecido com isto:

#!/usr/bin/perl

use strict;   # get perl to warn us if we try to use an undeclared variable.

# get all words on first line, and store them in a hash
#
# note: it doesn't matter which line we get the word list from because
# we only want to know if all lines have the same number of identical
# words.
my %words=();
$words{$_}++ for split (/\s+/,<>);

while(<>) {
  # now do the same for each subsequent line
  my %thisline=();
  $thisline{$_}++ for split;

  # and compare them.  exit with a non-zero exit code if they differ.
  if (%words != %thisline) {
    # optionally print a warning message to STDERR here
    exit 1;
  }
};

# add up the number of times each word was seen  on the first line  
my $count=0;
foreach (keys %words) {
  $count += $words{$_};
};

# print the total
print "$count\n";
exit 0;

A diferença significativa é o modo como os arrays com hash são preenchidos. Na primeira versão, apenas define o valor de cada chave ("palavra") como 1. Na segunda, conta o número de vezes que cada tecla é vista.

A segunda versão também tem que somar os valores para cada chave, não pode simplesmente imprimir o número de chaves visto.

    
por 16.01.2018 / 11:51
0

Para verificar se todas as linhas têm o mesmo número de palavras, basta verificar se todas as outras linhas têm o mesmo número de palavras que a primeira linha. Então, no Bash, conforme solicitado:

#!/bin/bash    
read -r -a arr;
count=${#arr[@]}
while read -r -a arr ; do 
        if [[ $count -ne ${#arr[@]} ]] ; then
                echo "not ok"
                exit 1
        fi
done
printf "ok, count=%d\n" $count

A saída é not ok ou ok, count=N .

Esta versão deve funcionar com todos os shells POSIXy (testado bash, dash, ksh e zsh):

#!/bin/sh    
[ "$ZSH_VERSION" ] && setopt sh_word_split
set -f
IFS= read -r line;
set -- $line
count=$#
while IFS= read -r line ; do 
        set -- $line
        if [ "$count" -ne "$#" ] ; then
                echo "not ok"
                exit 1
        fi
done
printf "ok, count=%d\n" "$count"

Ou use apenas awk :

awk 'NR==1 { c=NF; } 
     c != NF { print "not ok"; e=1; exit 1 } 
     END { if (!e) printf "ok, count=%d\n", c}' < input
    
por 16.01.2018 / 12:29
0

Não sei por que sempre sou acionado por /sed tags, mesmo se a contagem estiver envolvida:

sed '1{s/[^ ]* */#/g;h;d;};G;:a
  s/ [^ ]*\(\n\)#//;ta
  /^[^ ]*\n#$/!{s/.*/status:false count:NA/;q;};$!d
  s/.*/status:true count:0#0123456789+/;G;:b
  s/\(.\)\(#.*\)\(.\)\(.*\n\)#//;:o
  s/:+/:10/;s/\(.\)+\(.*#.*\)\(.\)//;to
  s/#.*\n$//; Tb' yourfile.txt

Código bonito e sustentável, não é? Poderia ser simplificado, se contar < = 99.

    
por 16.01.2018 / 13:46