Domanda

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

Questo è possibile al di fuori di un linguaggio di scripting a piena misura come Perl o Ruby? Preferirei farlo con sed o awk se possibile. Fondamentalmente, qualsiasi strumento UNIX-Y ampiamente leggero e ampiamente disponibile andrebbe bene, specialmente uno su cui posso imparare di più su rapidamente (Perl, quindi, non incluso.)

È stato utile?

Soluzione

Da Script a una linea utili 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 sia di spicco che tratranti da un file, è possibile combinare i comandi sopra in:

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

Altri suggerimenti

Quindi prenderò in prestito parte della risposta di @dogbane per questo, da questo sed Linea per rimuovere le linee vuote principali è 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 bisogno Efficienza, lo trovo più leggibile di tutto il resto finora.

Ecco una soluzione a un passaggio in AWK: non inizia a stampare fino a quando 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 linee vuote/non vuote (con [[:graph:]] e /^[[:space:]]*$/), le linee interne con solo spazio bianco saranno troncate per diventare veramente vuote.

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

Come menzionato 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 spoglia nuove linee, noi abbiamo

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

che non dipende da sed. Puoi usare echo -n Per spogliare la restante newline finale.

Ecco una versione SED adattata, che considera anche "vuote" quelle linee con solo spazi e schede.

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 cambiato a [^[:blank:]] (qualsiasi cosa non vuota) e il \n All'interno del secondo indirizzo di comando è stato modificato [[:space:]] Per consentire le nuove linee, spazi e schede.

Una versione alternativa, senza utilizzare le classi POSIX, ma il tuo SED deve supportare l'inserimento \t e \n dentro […]. Gnu Sed lo 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$

Usando bash

$ filecontent=$(<file)
$ echo "${filecontent/$'\n'}"

In Bash, usando gatto, wc, grep, sed, coda e testa:

# 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!

Per un'efficace versione non recursa della striscia di Newline (inclusi i personaggi "bianchi") l'ho sviluppato sed sceneggiatura.

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

Utilizza il buffer di attesa per archiviare tutte le righe vuote e le stamparle solo dopo aver trovato una linea non bianca. Qualcuno dovrebbe voler solo le nuove linee, è abbastanza per sbarazzarsi dei due [[:space:]]* parti:

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 a base di 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 è terminato dopo 15 minuti. Vincita :)

Per completezza della risposta, le linee principali che spogliano lo script SED sono già in streaming bene. Usa il più adatto per te.

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

UN bash soluzione.

Nota: solo utile Se il file è abbastanza piccolo essere letto in memoria contemporaneamente.

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file) Legge l'intero file e le rivestimenti trailing nuove linee, perché la sostituzione del comando ($(....)) implicitamente lo fa.
  • =~ è Bash Operatore di abbinamento a espressione regolare, e =~ ^$'\n'*(.*)$ facoltativamente corrisponde a qualsiasi primo nuove linee (avidamente) e cattura tutto ciò che viene dopo. Nota il potenzialmente confuso $'\n', che inserisce una nuova linea letterale usando ANSI C citando, perché sequenza di fuga \n non è supportato.
  • Si noti che questo particolare regex sempre corrispondenze, quindi il comando dopo && è sempre eseguito.
  • Variabile di array speciale BASH_REMATCH La rivincita contiene i risultati della corrispondenza regex più recente e dell'elemento array [1] Contiene ciò che la (prima e unica) sottoespressione tra parentesi (gruppo di acquisizione) catturata, che è la stringa di input con qualsiasi neofente principale spogliata. L'effetto netto è quello ${BASH_REMATCH[1]} Contiene il contenuto del file di input con nuove linee leader e finali spogliate.
  • Nota che la stampa con echo Aggiunge una singola nuova linea finale. Se vuoi evitarlo, usa echo -n invece (o utilizzare il più portatile printf '%s').

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

@Dogbane ha una bella risposta semplice per rimuovere le principali linee vuote. Ecco un semplice comando awk che rimuove solo le linee finali. Usalo con il comando SED di @Dogbane per rimuovere gli spazi tramite e finali.

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

Questo è piuttosto 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 tamponate e mai visualizzate sono gli spazi straordinari.

Ho usato printf invece di stampare per evitare l'aggiunta automatica di una nuova linea, poiché sto usando le nuove linee per separare già le linee nel buffer.

Questa sceneggiatura Awk farà il trucco:

BEGIN {
    ne=0;
}

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

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

L'idea è semplice: le righe vuote non vengono echeggiate immediatamente. Invece, aspettiamo che otteniamo una linea non vuota, e solo allora facciamo eco a tutte le linee vuote che si vedono prima, e solo allora eco echeggono dalla nuova linea non vuota.

perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top