grepping array do arquivo e reutilizando o padrão de pesquisa

1

Eu tenho um projeto que estou tentando realizar por meio de scripts de shell.

Eu tenho cerca de 30 anos de diretórios de um programa de rádio semanal de longa duração. Porque eles são de várias fontes, os nomes podem estar em formatos totalmente diferentes. Isso dificulta saber quais programas eu tenho e o que mostro que estou perdendo.

Eu quero criar links simbólicos em um formato de data padrão e vincular o nome do arquivo como data para o diretório de show real, se eu o tiver.

Por exemplo, quero fazer

'2015-09-25' -> '../Radio Show/2015-09-25 Special Guest/'
'2015-10-02' -> '../Radio Show/Very funny! 2015-10-02 Show'

Também há variedades de formatos de data, mas por enquanto estou preocupado em encontrar os formatos YY-MM-DD e YYYY-MM-DD.

Por isso, criei um arquivo em que cada linha era uma data, de 1980-01-01 a 2010-12-31 , usando esta resposta .

Depois, eu leio cada linha, usando find para procurar um diretório com essa string em seu nome. No entanto, demorou muito tempo para fazer um find em toda a árvore de diretórios para cada data de 30 anos *.

Então, usei find -type d . > filesystem.txt para criar um arquivo com todos os nomes de diretório. Então eu poderia apenas grep para cada string de data naquele arquivo, em vez de executar um find no disco para cada string de data.

No entanto, estou tendo um problema ao carregar cada linha do arquivo de datas no grep.

Eu posso obter correspondências usando $ grep -f dates.txt filesystem.txt Mas isso me dá todos os resultados, neste formato:

./complete/1996-02-18
./complete/1996-03-03
./complete/1996-03-31
...

e não consigo descobrir como fazer o resultado com o argumento string para obter isso:

'1996-03-31' -> './complete/1996-03-31'

Eu tentei $ grep "${dates[@]}" metadata/filesystem.txt , mas isso não faz o que eu pensava:

grep: 1988-01-03: No such file or directory
grep: 1988-01-04: No such file or directory

Aqui está uma versão com código psuedo do que eu quero fazer:

foreach ( date-string in dates.txt ) {
  grep date-string in filesystem.txt
  if (match) {
     ln -s match date-string
  }
}

Como posso fazer isso no bash?

- * Eu poderia simplificar isso não usando todas as datas, mas não tenho certeza se o programa de rádio caiu no mesmo dia para toda a sua história. Eu gostaria de ter certeza e não perder uma data, então eu quero usar todas as datas no período de 30 anos.

    
por user394 30.07.2016 / 06:31

5 respostas

3

Para responder a pergunta no assunto: como usar o grep para encontrar qualquer um dos elementos de um array .

a=(foo bar baz)
grep "${a[@]}" files

Seria:

grep foo bar baz files

Ou seja, pesquise foo em bar , baz ou files , o que você não deseja aqui.

Você quer:

grep 'foo
bar
baz' files

em vez disso. Para isso, você faria:

IFS=$'\n'
grep -- "${a[*]}" files

O primeiro caractere de $IFS é usado para unir os elementos da matriz ao usar a sintaxe "${a[*]}" . Isso funciona com todos os shells que suportam arrays ( ksh , zsh , bash , yash (embora a parte $'\n' não funcione em yash , você precisaria usar uma nova linha literal personagem lá)).

Com zsh , você também pode fazer:

grep -e$^a files

Que é expandido para

grep -efoo -ebar -ebaz files

Qual é outra maneira de pesquisar por diferentes strings.

(observe que, se a matriz contiver cadeias de caracteres fixas para pesquisar em oposição a expressões regulares para correspondência, você deverá usar a opção -F ).

    
por 30.07.2016 / 10:06
1

com zsh :

autoload zmv # best in ~/.zshrc
zmv -Ls -n '../Radio Show/(^*[0-9])((19|)(<80-99>~^??)|(20|)(<0-16>~^??))(-<1-12>-<1-31>~^-??-??)(^[0-9]*)' '${4:+19$4}${6:+20$6}$7'

-n é para execução a seco. Remova para realmente fazer o link quando estiver satisfeito com as ações propostas.

zmv cuida de evitar conflitos ou de substituir arquivos. Os operadores globais zsh glob aqui:

  • <1-12> corresponde a uma string que é resolvida para um inteiro decimal entre 1 e 12. Observe que ela corresponde ao 012 em 2012.
  • ^x : negação
  • x~y (e não): string que corresponde a x, desde que não corresponda a y. Portanto, <1-12>~^?? corresponde a um número de 2 dígitos de 1 a 12 (corresponde a 01, mas não a 1, nem a 0001).
  • (x|y) : alternação como em ERE.

Ele insere os 19 ou 20 ausentes para datas no formato AA-MM-DD.

    
por 30.07.2016 / 09:53
1

A resposta de John1024 provavelmente é a melhor, mas apenas para completar está sua implementação de pseudocódigo:

for datestring in $(cat dates.txt)
do if match="$(grep "$datestring" filesystem.txt)"
   then echo ln -s "$match" "$datestring"
   fi
done

Eu deixei um echo para que ele não faça nada até que você o remova. Mas o acima tem que expandir todas as datas como argumentos, então você deve preferir isto:

while read datestring
do if match="$(grep "$datestring" filesystem.txt)"
   then echo ln -s "$match" "$datestring"
   fi
done <dates.txt

Coloquei $datestring entre aspas duplas, embora saibamos que não tem espaços, portanto, isso não alterará nada.

    
por 30.07.2016 / 11:38
1

De acordo com o título da pergunta, o problema é que você tem uma matriz contendo uma lista de strings que você deseja buscar, mas o grep requer uma única expressão regular ou várias opções -e .

(você pode usar -f e fornecer as strings de um arquivo ou pipe ou processo de substituição, mas minha resposta é focada em usar uma matriz. ou apenas uma lista de strings).

A abordagem simples, de fazer loop em cada elemento da matriz e gerar o | como um prefixo ou sufixo para cada elemento, acaba com uma string que possui um | inicial ou final a ser removido. Facilmente feito, mas irritante ... e você não deveria ter que, deve haver uma função interna que une strings arbitrárias com um separador arbitrário.

perl tem uma função join() muito útil que pode ser usada para esse tipo de coisa. Infelizmente, bash não ... mas é fácil criar um.

function join() {
  # input:
  # $1       - separator string
  # $2...$n  - list of items to join
  #
  # output:
  # a joined string on stdout.

  local sep result i

  sep="$1" ; shift
  result="$1" ; shift

  for i in "$@" ; do result="$result$sep$i" ; done
  printf '%s' "$result"
}

Observação: essa join function deliberadamente não anexa uma nova linha ao final da string de saída. Você pode adicionar facilmente um, se precisar (por exemplo, com echo , como nos exemplos abaixo). Também seria fácil adicionar uma opção para alterar a string printf format para %s\n ... como uma primeira opção opcional antes do argumento do separador ou, para fazer isso corretamente, usando getopts .

Observe também: o nome da função join está em conflito com o utilitário padrão join (que associa linhas correspondentes de dois arquivos). Eu nunca uso /usr/bin/join (porque nunca coincide exatamente com o que eu preciso fazer, então eu acabo tendo que escrever um script perl ou awk), então eu não me importo. Eu sempre posso substituir a função com command join file1 file2 ou similar de qualquer maneira, se eu precisar. Se você se importa, renomeie para joinarray ou joinstrings ou algo assim. Eu prefiro join para que seja o mesmo nome da função perl .

De qualquer forma, com essa função em seu ambiente de shell ou em seu script, você pode fazer coisas como:

$ join '|' a b c d e
a|b|c|d|e$ _

(isso é feio, não há nova linha, então o próximo prompt $ aparece na mesma linha que a saída. use echo enquanto testa esta função na linha de comando)

$ echo $(join '|' a b c d e)
a|b|c|d|e

$ echo $(join '|' a b c 'd e')
a|b|c|d e

$ echo '^('$(join '|' a b c 'd e' 'f g h')')$'
^(a|b|c|d e|f g h)$

Lembre-se de citar e / ou escapar de qualquer texto que precise.

Esta função é útil para mais do que apenas os operadores regexp | alternation ( OR ).

$ csv=$(join ',' foo bar baz)
$ echo "$csv"
foo,bar,baz

$ echo $(join ':' user pass uid gid gecos home shell)
user:pass:uid:gid:gecos:home:shell

ou juntando uma matriz:

date_array=( $(cat dates.txt) )
date_ere='('$(join '|' "${date_array[@]}")')'      # extended regexp
date_re='\('$(join '\|' "${date_array[@]}")'\)'    # basic regexp

grep "$date_re" filesystem.txt

ou até mesmo:

grep -E "($(join '|' $(cat dates.txt)))" filesystem.txt

awk também não tem uma função join . aqui está a mesma função implementada para o awk. As funções bash e awk são modeladas no separador perl function - como primeiro argumento, matriz / lista como argumentos subseqüentes.

function join(sep,array,       i) {
  result=array[1];
  for (i=2;i<=length(array);i++) result = result sep array[i];
  return result;
};
    
por 31.07.2016 / 07:16
0

Se eu entendi corretamente, você tem um arquivo filesystem.txt que se parece com:

$ cat filesystem.txt 
../Radio Show/Very funny! 2015-10-02 Show
../Radio Show/2015-09-25 Special Guest/

Considere isso:

$ sed -E 's/.*[^[:digit:]]([[:digit:]]{2,4}-[[:digit:]]{2}-[[:digit:]]{2}).*/ln -s "&" ""/' filesystem.txt >script

O acima cria um arquivo chamado script . script parece uma série de comandos bash :

$ cat script
ln -s "../Radio Show/Very funny! 2015-10-02 Show" "2015-10-02"
ln -s "../Radio Show/2015-09-25 Special Guest/" "2015-09-25"

Inspecione este arquivo e, se parecer que está fazendo o que você deseja, execute-o:

bash script
    
por 30.07.2016 / 07:57