Come rimuovere l'ultima riga di tutti i file da una directory?


17

Ho molti file di testo in una directory e desidero rimuovere l'ultima riga di ogni file nella directory.

Come posso farlo?


6
Che cosa hai provato? unix.stackexchange.com/help/how-to-ask : "Condividere la tua ricerca aiuta tutti. Dicci cosa hai trovato e perché non ha soddisfatto le tue esigenze. Ciò dimostra che hai impiegato del tempo per cercare di aiutarti , ci salva dal ribadire risposte ovvie e, soprattutto, ti aiuta a ottenere una risposta più specifica e pertinente! "
Patrick,

Perché il pensiero di qualcuno che lo applica accidentalmente a / etc ha una qualità molto speciale di induzione da
rabbia

Risposte:


4

Se hai accesso a vim, puoi usare:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done

16

Puoi usare questo simpatico oneliner se hai GNU sed.

 sed -i '$ d' ./*

Rimuoverà l'ultima riga di ogni file non nascosto nella directory corrente. Switch -iper GNU sedsignifica operare sul posto e '$ d'comanda seddi eliminare l'ultima riga (che $significa l'ultima e che dsignifica cancellare).


3
Questo genererà errori (e non farà nulla) se la cartella contiene qualcosa di diverso da un normale file, ad esempio un'altra cartella ...
Najib Idrissi

1
@StefanR Stai usando -iquale è un GNUismo, quindi questo è discutibile, ma perderei la mia vecchia barba se non riuscissi a sottolineare che alcune versioni precedenti di sednon ti permettono di mettere uno spazio bianco tra il $e il d(o, in generale, tra lo schema e il comando).
zwol,

1
@zwol Come ho scritto, questo comporterà un errore , non un avvertimento, e sed si arrenderà una volta raggiunto quel file (almeno con la versione di sed che ho). I file successivi non verranno elaborati. Gettare via i messaggi di errore sarebbe un'idea terribile dal momento che non sapresti nemmeno che fosse successo! Con zsh puoi usare *(.)glob per i file regolari, non conosco altre shell.
Najib Idrissi,

@NajibIdrissi Hmm, hai ragione. Questo mi sorprende; Mi sarei aspettato che si lamentasse della directory ma poi passasse al file successivo sulla riga di comando. In effetti, penso che lo segnalerò come un bug.
zwol,

@don_crissti Ho anche GNU sed v4.3 ... Non so cosa dirti, ho appena provato di nuovo. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Najib Idrissi

11

Le altre risposte hanno tutti problemi se la directory contiene qualcosa di diverso da un normale file o un file con spazi / righe nel nome del file. Ecco qualcosa che funziona indipendentemente:

find "$dir" -type f -exec sed -i '$d' '{}' '+'
  • find "$dir" -type f: trova i file nella directory $dir
    • -type f che sono file regolari;
    • -exec eseguire il comando su ogni file trovato
    • sed -i: modifica i file in atto;
    • '$d': elimina ( d) l'ultima ( $) riga.
    • '+': dice a find di continuare ad aggiungere argomenti sed(un po 'più efficiente dell'esecuzione del comando per ogni file separatamente, grazie a @zwol).

Se non vuoi scendere nelle sottodirectory, puoi aggiungere l'argomento -maxdepth 1a find.


1
Hmm, ma a differenza delle altre risposte questo scende nelle sottodirectory. (Inoltre, con le versioni attuali di findquesto è scritto in modo più efficiente find $dir -type f -exec sed -i '$d' '{}' '+'.)
zwol

@zwol Grazie, l'ho aggiunto nella risposta.
Najib Idrissi,

-print0non è presente nel comando completo, perché inserirlo nella spiegazione?
Ruslan,

1
Inoltre, -depth 0non funziona (findutils 4.4.2), dovrebbe invece essere -maxdepth 1.
Ruslan,

@Ruslan Avevo una prima versione in cui utilizzavo xargs ma poi ricordavo -exec.
Najib Idrissi,

9

Usare GNU sed -i '$d'significa leggere l'intero file e crearne una copia senza l'ultima riga, mentre sarebbe molto più efficiente troncare il file in atto (almeno per file di grandi dimensioni).

Con GNU truncatepuoi fare:

for file in ./*; do
  [ -f "$file" ] &&
    length=$(tail -n 1 "$file" | wc -c) &&
    [ "$length" -gt 0 ] &&
    truncate -s "-$length" "$file"
done

Se i file sono relativamente piccoli, sarebbe probabilmente meno efficiente poiché esegue diversi comandi per file.

Nota che per i file che contengono byte extra dopo l'ultimo carattere di nuova riga (dopo l'ultima riga) o in altre parole se hanno un'ultima riga non delimitata , a seconda taildell'implementazione, tail -n 1verranno restituiti solo quei byte extra (come GNU tail), o l'ultima riga (correttamente delimitata) e quei byte extra.


avete bisogno di un |wc -cnella tailchiamata? (o a ${#length})
Jeff Schaller

@JeffSchaller. Ops. wc -c era inteso davvero. ${#length}non funzionerebbe poiché conta i caratteri, non i byte e $(...)rimuove il carattere di coda nuova in modo ${#...}da essere disattivato di uno anche se tutti i caratteri fossero a byte singolo.
Stéphane Chazelas,

6

Un approccio più portatile:

for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done

Non penso che questo abbia bisogno di spiegazioni ... tranne forse che in questo caso dè lo stesso di $dpoiché edseleziona di default l'ultima riga.
Questo non cercherà ricorsivamente e non elaborerà i file nascosti (aka dotfile).
Se vuoi modificare anche quelli vedi Come abbinare * con i file nascosti all'interno di una directory


Bello! Se cambi [[]]a []allora sarà completamente conforme a POSIX. ( [[ ... ]]is a Bashism.)
Wildcard

@Wildcard - grazie, cambiato (anche se [[non è un bashismo )
don_crissti

Un non-POSIX-ism, dovrei dire. :)
Wildcard

3

One-liner conforme a POSIX per tutti i file a partire ricorsivamente dalla directory corrente, inclusi i file di punti:

find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Solo per .txtfile, in modo non ricorsivo:

find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Vedi anche:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.