Ho molti file di testo in una directory e desidero rimuovere l'ultima riga di ogni file nella directory.
Come posso farlo?
Ho molti file di testo in una directory e desidero rimuovere l'ultima riga di ogni file nella directory.
Come posso farlo?
Risposte:
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 -i
per GNU sed
significa operare sul posto e '$ d'
comanda sed
di eliminare l'ultima riga (che $
significa l'ultima e che d
significa cancellare).
-i
quale è un GNUismo, quindi questo è discutibile, ma perderei la mia vecchia barba se non riuscissi a sottolineare che alcune versioni precedenti di sed
non ti permettono di mettere uno spazio bianco tra il $
e il d
(o, in generale, tra lo schema e il comando).
*(.)
glob per i file regolari, non conosco altre shell.
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 trovatosed -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 1
a find
.
find
questo è scritto in modo più efficiente find $dir -type f -exec sed -i '$d' '{}' '+'
.)
-print0
non è presente nel comando completo, perché inserirlo nella spiegazione?
-depth 0
non funziona (findutils 4.4.2), dovrebbe invece essere -maxdepth 1
.
-exec
.
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 truncate
puoi 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 tail
dell'implementazione, tail -n 1
verranno restituiti solo quei byte extra (come GNU tail
), o l'ultima riga (correttamente delimitata) e quei byte extra.
|wc -c
nella tail
chiamata? (o a ${#length}
)
${#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.
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 $d
poiché ed
seleziona 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
[[]]
a []
allora sarà completamente conforme a POSIX. ( [[ ... ]]
is a Bashism.)
[[
non è un bashismo )
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 .txt
file, 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: