Question

Je voudrais supprimer toutes les lignes vides à partir d'un fichier, mais seulement quand ils sont à la fin / début d'un fichier (qui est, s'il n'y a pas de lignes non vides avant de les, au début, et s'il n'y a pas de lignes non vides après eux, à la fin.)

Est-ce possible en dehors d'un langage de script entièrement présenté comme Perl ou Ruby? Je préfère le faire avec sed ou awk si possible. Fondamentalement, tout léger et largement disponible outil UNIX-y serait bien, surtout que je peux apprendre plus sur rapidement (Perl, donc pas inclus.)

Était-ce utile?

La solution

De scripts d'une ligne utiles pour 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

Par conséquent, pour supprimer à la fois avant et arrière des lignes vides à partir d'un fichier, vous pouvez combiner les commandes ci-dessus en:

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

Autres conseils

Je vais emprunter une partie de la réponse de @ dogbane pour cela, puisque cette ligne de sed pour enlever les grandes lignes vides est si courte ...

tac fait partie de coreutils , et inverse un fichier. Donc, faire deux fois:

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

Il est certainement pas le plus efficace, mais à moins que vous besoin l'efficacité, je trouve plus facile à lire que tout le reste jusqu'à présent.

Voici une solution d'une passe en awk: elle ne commence pas l'impression jusqu'à ce qu'il voit une ligne non vide et quand il voit une ligne vide, il se souvient jusqu'à la prochaine ligne non vide

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

Note, en raison du mécanisme que je utilise pour considérer les lignes vides / non vides (avec [[:graph:]] et /^[[:space:]]*$/), des lignes intérieures avec que des espaces seront tronqués pour devenir vraiment vide.

en utilisant 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

Comme mentionné dans une autre réponse , tac fait partie de coreutils et inverse un fichier. La combinaison de l'idée de le faire deux fois avec le fait que la volonté de substitution de commande dépouillent arrière nouvelles lignes , nous obtenons

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

qui ne dépend pas de sed. Vous pouvez utiliser echo -n pour dépouiller le saut de ligne restante de fuite hors route.

Voici une version adaptée sed, qui considère aussi « vide » ces lignes avec des espaces et des onglets juste là-dessus.

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

Il est essentiellement la version de réponse acceptée (en tenant compte commentaire bryanh), mais le . de points dans la première commande a été changé pour [^[:blank:]] (quoi que ce soit pas vide) et la \n dans la seconde adresse de commande a été changé pour [[:space:]] pour permettre des sauts de ligne, les espaces d'un onglets.

Une autre version, sans utiliser les classes POSIX, mais votre soutien doit sed insérer \t et \n à l'intérieur […]. GNU sed ne, BSD sed ne fonctionne pas.

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$

Utilisation bash

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

bash, en utilisant chat, wc, grep, sed, la queue et la tête:

# 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

L'homme, il vaut vraiment la peine d'apprendre la langue de programmation « réel » pour éviter que la laideur!

Pour une version non-récursive efficace de la bande de retour à la ligne de fuite (y compris les caractères « blancs ») J'ai développé ce script sed.

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

Il utilise la mémoire tampon de maintien pour stocker toutes les lignes vierges et imprime QU'EN après qu'il trouve une ligne non vide. Si quelqu'un veut que les nouvelles lignes, il suffit de se débarrasser des deux parties de [[:space:]]*:

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

J'ai essayé une simple comparaison de la performance avec le script récursive bien connu

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

sur un fichier 3MB avec 1 Mo de lignes vides aléatoires autour d'un texte base64 aléatoire.

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

Le script de streaming a environ 0,5 seconde pour terminer, l'récursive n'a pas mis fin au bout de 15 minutes. Win:)

Par souci l'exhaustivité de la réponse, les lignes menant décapage script sed est déjà en streaming bien. Utilisez le plus approprié pour vous.

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

Une solution bash .

Note:. Seulement utile si le fichier est assez petit pour être lu en mémoire à la fois

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file) lit le fichier entier et versions trailing sauts de ligne, parce que la substitution de commande ($(....)) implicitement fait ça.
  • =~ est de bash opérateur de correspondance d'expression régulière , et =~ ^$'\n'*(.*)$ correspond éventuellement tout conduisant (retour à la ligne avidement), et capture tout ce qui vient après. Notez la $'\n' pouvant prêter à confusion, qui insère un saut de ligne littérale en utilisant ANSI C citant , parce que la séquence d'échappement \n est pas pris en charge.
  • Notez que cette regex particulière toujours matches, de sorte que la commande après && est toujours exécuté.
  • variable tableau spécial rematch BASH_REMATCH contient les résultats du plus récent match de regex et élément de tableau [1] contient ce que le (premier et seulement) parenthésée sous-expression (groupe de capture) capturé, qui est la chaîne d'entrée avec tous les grands sauts de ligne dénudés. L'effet net est que ${BASH_REMATCH[1]} contient le contenu du fichier d'entrée avec les deux nouvelles lignes avant et arrière dénudés.
  • Notez que l'impression avec echo ajoute un saut de ligne de fuite. Si vous voulez éviter cela, utilisez echo -n au lieu (ou utilisez le printf '%s' plus portable).

Je voudrais vous présenter une autre variante pour 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 a une réponse simple agréable pour enlever grandes lignes vides. Voici une simple commande awk qui supprime simplement les lignes de fuite. Utilisez ceci avec la commande @ sed de supprimer à la fois dogbane pour les blancs de fuite.

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

Ceci est assez simple en fonctionnement.

  • Ajoutez chaque ligne à un tampon comme nous le lisons.
  • Pour chaque ligne qui contient un caractère, imprimer le contenu de la mémoire tampon puis l'effacer.

Alors, les seules choses qui se tamponnés et ne sont affichés des espaces de fin.

J'utilisé printf au lieu d'impression pour éviter l'ajout automatique d'une nouvelle ligne, puisque je suis en utilisant les nouvelles lignes pour séparer déjà les lignes dans le tampon.

Ce script AWK fera l'affaire:

BEGIN {
    ne=0;
}

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

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

L'idée est simple: les lignes vides ne sont pas immédiatement répercutée. Au lieu de cela, nous attendons jusqu'à ce que nous obtenons une ligne non vide, et alors seulement nous avons d'abord l'écho comme autant de lignes vides comme on le voit devant elle, et alors seulement font écho à la nouvelle ligne non vide.

perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top