Como posso assistir a 17ª (ou última, se for menor) linha nos arquivos de uma pasta?

8

Atualmente estou usando

watch head -n 17 *

que funciona, mas também mostra todas as linhas até o dia 17. Basicamente, gostaria de mostrar apenas a última linha de cada arquivo que é mostrado com minha abordagem atual. Como posso conseguir isso?

Exemplo

Por uma questão de exemplo, vamos reduzir a linha nr. para 7. Então:

Arquivo de exemplo:

1
2
3
4
5
6
7
8

está linha:

watch head -n 7 *

saídas

1
2
3
4
5
6
7

onde eu quero:

7
Felix Dombek
fonte

Respostas:

15

Com o GNU awk:

watch -x gawk '
  FNR == 17 {nextfile}
  ENDFILE   {if (FNR) printf "%15s[%02d] %s\n", FILENAME, FNR, $0}' ./*

O que fornece uma saída como:

        ./file1[17] line17
  ./short-file2[05] line 5 is the last

Observe que a ./*glob é expandida apenas uma vez no momento em que watché invocada.

Sua watch head -n 17 *era uma vulnerabilidade de injeção de comando arbitrária, pois a expansão *foi realmente interpretada como código do shell pelo shell que watchinvoca para interpretar a concatenação de seus argumentos com espaços.

Se houvesse um arquivo chamado $(reboot)no diretório atual, ele seria reiniciado.

Com -x, estamos dizendo watchpara pular o shell e executar o comando diretamente. Como alternativa, você pode fazer:

watch 'exec gawk '\''
  FNR == 17 {nextfile}
  ENDFILE   {if (FNR) printf "%15s[%02d] %s\n", FILENAME, FNR, $0}'\'' ./*'

Para watchexecutar um shell que expandiria esse ./*globo a cada iteração. watch foo baré efetivamente o mesmo que watch -x sh -c 'foo bar'. Ao usar watch -x, você pode especificar qual shell você deseja e, por exemplo, escolher um shell mais poderoso como zshesse que pode fazer globbing recursivo e restringir a arquivos regulares:

watch -x zsh -c 'awk '\''...'\'' ./**/*(.)'

Sem gawk, você ainda pode fazer algo como:

watch '
  for file in ./*; do
    [ -s "$file" ] || continue
    printf "%s: " "$file"
    head -n 17 < "$file" | tail -n 1
  done'

Dando uma saída como:

./file1: line17
./short-file2: line 5 is the last

Mas isso seria muito menos eficiente, pois implica na execução de vários comandos por arquivo.

Stéphane Chazelas
fonte
Obrigado pela nota de segurança. Na verdade, você está certo e meu comando também não fez o que eu precisava, ou seja, observe os arquivos adicionados, que sua versão também resolve. A única desvantagem é que eu preciso aprender AWK, eu tinha que isso seria feito com find . -execou sth como este: D
Felix Dombek
11

Combine heade tailcomo nestes dois exemplos:

$ seq 1 80 | head -n 17 | tail -n 1
17

$ seq 1 10 | head -n 17 | tail -n 1
10

Portanto, para resolver seu problema real, o comando é:

watch 'for f in *; do head -n 17 -- "$f" 2>/dev/null | tail -n 1 ; done'

Observe a 2>/dev/nullpeça, é necessária porque * corresponderá aos diretórios e aos arquivos que você talvez não tenha permissão para ler, o que produzirá uma mensagem de erro que provavelmente você deseja ocultar.

hyde
fonte
0

outra abordagem com find, exec e sed

watch find . -type f -name \"*\" -exec sh -c \'\( printf '%-50s ' {} \; sed -n 17p {}\)\' \\\;
dhanlin
fonte
11
Não funciona para arquivos com menos de 17 linhas. Leia a pergunta com atenção.
Curinga