Wie kann ich die 17. (oder letzte, wenn weniger) Zeile in Dateien eines Ordners anzeigen?

8

Ich benutze gerade

watch head -n 17 *

das funktioniert, zeigt aber auch alle Zeilen bis zum 17 .. Grundsätzlich möchte ich nur die letzte Zeile für jede Datei anzeigen, die mit meinem aktuellen Ansatz angezeigt wird. Wie kann ich das erreichen?

Beispiel

Reduzieren wir zum Beispiel die Zeile nr. bis 7. Also:

Beispieldatei:

1
2
3
4
5
6
7
8

diese Linie:

watch head -n 7 *

Ausgänge

1
2
3
4
5
6
7

wo ich will:

7
Felix Dombek
quelle

Antworten:

15

Mit GNU awk:

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

Welches gibt eine Ausgabe wie:

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

Beachten Sie, dass der ./*Glob zum Zeitpunkt des Aufrufs nur einmal erweitert watchwird.

Es handelte sich watch head -n 17 *um eine Sicherheitsanfälligkeit bezüglich willkürlicher Befehlsinjektion, da deren Erweiterung *von der Shell, watchdie die Interpretation der Verkettung ihrer Argumente mit Leerzeichen aufruft, tatsächlich als Shell-Code interpretiert wurde.

Wenn $(reboot)im aktuellen Verzeichnis eine Datei aufgerufen würde, würde sie neu gestartet.

Mit -xsagen wir, dass wir watchdie Shell überspringen und den Befehl direkt ausführen sollen. Alternativ können Sie Folgendes tun:

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

Zum watchAusführen einer Shell, die diesen ./*Glob bei jeder Iteration erweitert. watch foo barist in der Tat das gleiche wie watch -x sh -c 'foo bar'. Bei der Verwendung watch -xkönnen Sie angeben, welche Shell Sie möchten, und beispielsweise eine leistungsstärkere Shell auswählen zsh, die rekursives Globbing ausführen und sich auf reguläre Dateien beschränken kann:

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

Ohne gawkkönnten Sie noch etwas tun wie:

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

Geben Sie eine Ausgabe wie:

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

Dies wäre jedoch viel weniger effizient, da mehrere Befehle pro Datei ausgeführt werden müssen.

Stéphane Chazelas
quelle
Danke für den Sicherheitshinweis. Eigentlich hast du recht und mein Befehl hat auch nicht das getan, wofür ich ihn brauchte, dh hinzugefügte Dateien ansehen, die deine Version auch löst. Der einzige Nachteil ist, dass ich AWK lernen muss, ich hatte jedoch gedacht, dass dies mit find . -execoder so gemacht würde : D
Felix Dombek
11

Kombinieren headund tailwie in diesen beiden Beispielen:

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

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

Um Ihr eigentliches Problem zu lösen, lautet der Befehl:

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

Hinweis zu dem 2>/dev/nullTeil, der benötigt wird, da * mit Verzeichnissen und Dateien übereinstimmt, für die Sie möglicherweise keine Leseberechtigung haben. Dies führt zu einer Fehlermeldung, die Sie wahrscheinlich ausblenden möchten.

Hyde
quelle
0

ein anderer Ansatz mit find, exec und sed

watch find . -type f -name \"*\" -exec sh -c \'\( printf '%-50s ' {} \; sed -n 17p {}\)\' \\\;
Dhanlin
quelle
1
Funktioniert nicht für Dateien mit weniger als 17 Zeilen. Lesen Sie die Frage sorgfältig durch.
Wildcard