it-swarm.it

Rimozione di trailing/avvio di newline con sed, awk, tr e friends

Vorrei rimuovere tutte le righe vuote da un file, ma solo quando sono alla fine/all'inizio di un file (cioè, se non ci sono linee non vuote prima di esse, all'inizio e se ci sono nessuna riga non vuota dopo di loro, alla fine.)

È possibile ciò al di fuori di un linguaggio di scripting completo come Perl o Ruby? Preferirei farlo con sed o awk se possibile. Fondamentalmente, qualsiasi strumento UNIX-y leggero e ampiamente disponibile andrebbe bene, specialmente uno che posso imparare di più su velocemente (Perl, quindi, non incluso.)

31
ELLIOTTCABLE

DaUtili script a riga singola per sed:

# Delete all leading blank lines at top of file (only).
sed '/./,$!d' file

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

Pertanto, per rimuovere le righe vuote iniziali e finali da un file, è possibile combinare i comandi sopra riportati in:

sed -e :a -e '/./,$!d;/^\n*$/{$d;N;};/\n$/ba' file
44
dogbane

Quindi prenderò in prestito parte della risposta di @ dogbane per questo, dal momento che la riga sed per rimuovere le prime righe vuote è così breve ...

tac fa parte di coreutils , e inverte un file. Quindi fallo due volte:

tac file | sed -e '/./,$!d' | tac | sed -e '/./,$!d'

Non è certamente il più efficiente, ma a meno che tu need efficiency, lo trovo più leggibile di tutto il resto finora.

10
Izkata

ecco una soluzione one-pass in awk: non inizia a stampare finché non vede una linea non vuota e quando vede una linea vuota, la ricorda fino alla prossima linea non vuota

awk '
    /[[:graph:]]/ {
        # a non-empty line
        # set the flag to begin printing lines
        p=1      
        # print the accumulated "interior" empty lines 
        for (i=1; i<=n; i++) print ""
        n=0
        # then print this line
        print
    }
    p && /^[[:space:]]*$/ {
        # a potentially "interior" empty line. remember it.
        n++
    }
' filename

Nota, a causa del meccanismo che sto usando per considerare le linee vuote/non vuote (con [[:graph:]] e /^[[:space:]]*$/), le linee interne con solo spazi bianchi saranno troncate per diventare veramente vuote.

3
glenn jackman

usando awk:

awk '{a[NR]=$0;if($0 && !s)s=NR;}
    END{e=NR;
        for(i=NR;i>1;i--) 
            if(a[i]){ e=i; break; } 
        for(i=s;i<=e;i++)
            print a[i];}' yourFile
2
Kent

Come citato in un'altra risposta , tac fa parte di coreutils , e inverte un file. Combinando l'idea di farlo due volte con il fatto che la sostituzione del comando toglierà le nuove righe finali , otteniamo

echo "$(echo "$(tac "$filename")" | tac)"

che non dipende da sed. È possibile utilizzare echo -n per rimuovere la rimanente riga finale finale.

2
Jason Gross

Ecco una versione sed adattata, che considera anche "vuote" quelle linee con solo spazi e tabulazioni su di essa.

sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}'

È fondamentalmente la versione di risposta accettata (considerando il commento di BryanH), ma il punto . nel primo comando è stato modificato in [^[:blank:]] (tutto non vuoto) e il \n all'interno del secondo indirizzo di comando è stato modificato in [[:space:]] per consentire a newlines, spazi a schede.

Una versione alternativa, senza utilizzare le classi POSIX, ma la tua sed deve supportare l'inserimento di \t e \n all'interno […]. GNU sed fa, BSD sed no.

sed -e :a -e '/[^\t ]/,$!d; /^[\n\t ]*$/{ $d; N; ba' -e '}'

Test:

Prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' 



foo

foo



Prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -n l
$
 \t $
$
foo$
$
foo$
$
 \t $
$
Prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}'
foo

foo
Prompt$
2
Aurelio Jargas

Per un'efficace versione non ricorsiva della striscia dei newlines finali (compresi i caratteri "bianchi") ho sviluppato questo script sed.

sed -n '/^[[:space:]]*$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H'

Usa il buffer di attesa per memorizzare tutte le righe vuote e le stampa solo dopo aver trovato una riga non vuota. Se qualcuno vuole solo la nuova riga, è sufficiente eliminare le due parti [[:space:]]*:

sed -n '/^$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^$/H'

Ho provato un semplice confronto delle prestazioni con il noto script ricorsivo

sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba'

su un file da 3 MB con 1 MB di righe vuote casuali attorno a un testo base64 casuale.

shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M > bigfile
base64 </dev/urandom | dd bs=1 count=1M >> bigfile
shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M >> bigfile

Lo script di streaming ha richiesto circa 0,5 secondi per essere completato, il ricorsivo non si è concluso dopo 15 minuti. Vinci :)

Per completezza della risposta, le linee guida che rimuovono la sceneggiatura sed stanno già andando bene. Usa il più adatto a te.

sed '/[^[:blank:]]/,$!d'
sed '/./,$!d'
1
tlwhitec

In bash, usando cat, wc, grep, sed, tail e head:

# number of first line that contains non-empty character
i=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | head -1`
# number of hte last one
j=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | tail -1`
# overall number of lines:
k=`cat <your_file> | wc -l`
# how much empty lines at the end of file we have?
m=$(($k-$j))
# let strip last m lines!
cat <your_file> | head -n-$m
# now we have to strip first i lines and we are done 8-)
cat <your_file> | tail -n+$i

Amico, vale sicuramente la pena imparare un linguaggio di programmazione "reale" per evitare quella bruttezza!

1

Utilizzando bash

$ filecontent=$(<file)
$ echo "${filecontent/$'\n'}"
1
bash-o-logist

Vorrei introdurre un'altra variante per gawk v4.1 +

result=($(gawk '
    BEGIN {
        lines_count         = 0;
        empty_lines_in_head = 0;
        empty_lines_in_tail = 0;
    }
    /[^[:space:]]/ {
        found_not_empty_line = 1;
        empty_lines_in_tail  = 0;
    }
    /^[[:space:]]*?$/ {
        if ( found_not_empty_line ) {
            empty_lines_in_tail ++;
        } else {
            empty_lines_in_head ++;
        }
    }
    {
        lines_count ++;
    }
    END {
        print (empty_lines_in_head " " empty_lines_in_tail " " lines_count);
    }
' "$file"))

empty_lines_in_head=${result[0]}
empty_lines_in_tail=${result[1]}
lines_count=${result[2]}

if [ $empty_lines_in_head -gt 0 ] || [ $empty_lines_in_tail -gt 0 ]; then
    echo "Removing whitespace from \"$file\""
    eval "gawk -i inplace '
        {
            if ( NR > $empty_lines_in_head && NR <= $(($lines_count - $empty_lines_in_tail)) ) {
                print
            }
        }
    ' \"$file\""
fi
0
puchu

Questo script AWK farà il trucco:

BEGIN {
    ne=0;
}

/^[[:space:]]*$/ {
    ne++;
}

/[^[:space:]]+/ {
    for(i=0; i < ne; i++)
        print "";
    ne=0;
    print
}

L'idea è semplice: le linee vuote non vengono immediatamente echeggiate. Invece, aspettiamo di ottenere una linea non vuota, e solo allora prima echeggiamo tutte le linee vuote che abbiamo visto prima, e solo allora echeggiamo la nuova linea non vuota.

0
Adi Degani

Abash solution .

Nota: solo utile se il file è abbastanza piccolo per essere letto in memoria in una sola volta.

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file) legge l'intero file e taglia trailing newlines, perché la sostituzione di comando ($(....)) implicitamente lo fa.
  • =~ è l'operatore di corrispondenza espressione regolare di bash, e =~ ^$'\n'*(.*)$ corrisponde opzionalmente a qualsiasi leading newline (avidamente) e cattura ciò che viene dopo. Si noti il ​​$'\n' potenzialmente confusionario, che inserisce una newline letterale usando ANSI C quoting , perché la sequenza di escape \n non è supportata.
  • Si noti che questa particolare espressione regolare sempre corrisponde, quindi il comando dopo && è always eseguito.
  • La variabile di array speciale BASH_REMATCH rematch contiene i risultati della più recente corrispondenza di espressioni regolari e l'elemento di matrice [1] contiene ciò che viene catturato (primo e unico) sottoespressione parentesi (gruppo di cattura), che è la stringa di input con qualsiasi nuova riga iniziale rimossa. L'effetto netto è che ${BASH_REMATCH[1]} contiene il contenuto del file di input con le nuove righe iniziali e finali rimosse.
  • Si noti che la stampa con echo aggiunge una singola riga finale finale. Se vuoi evitarlo, usa invece echo -n (o usa il printf '%s' più portatile).
0
mklement0

@ dogbane ha una bella risposta semplice per rimuovere le prime linee vuote. Ecco un semplice comando awk che rimuove solo le righe finali. Usalo con il comando sed di @ dogbane per rimuovere sia spazi iniziali che finali.

awk '{ LINES=LINES $0 "\n"; } /./ { printf "%s", LINES; LINES=""; }'

Questo è abbastanza semplice in funzione. 

  • Aggiungi ogni riga a un buffer mentre lo leggiamo. 
  • Per ogni riga che contiene un carattere, stampare il contenuto del buffer e quindi cancellarlo.

Quindi le uniche cose che vengono bufferizzate e mai visualizzate sono gli spazi vuoti finali.

Ho usato printf invece di stampare per evitare l'aggiunta automatica di una nuova riga, dal momento che sto usando newline per separare le righe nel buffer già.

0
Andy Mortimer