web-dev-qa-db-fra.com

Suppression des nouvelles lignes de début/fin avec sed, awk, tr et friends

Je souhaite supprimer toutes les lignes vides d'un fichier, mais uniquement lorsqu'elles se trouvent à la fin/au début d'un fichier (c'est-à-dire, s'il n'y a pas de lignes non vides avant, au début; et s'il y a pas de lignes non vides après eux, à la fin.)

Est-ce possible en dehors d'un langage de script complet, tel que Perl ou Ruby? Je préférerais faire cela avec sed ou awk si possible. En gros, tout outil UNIX-y léger et largement disponible conviendrait parfaitement, en particulier un outil pour lequel je peux en apprendre plus rapidement (Perl, donc, non inclus.)

31
ELLIOTTCABLE

FromScripts 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 les lignes vides de début et de fin d'un fichier, vous pouvez combiner les commandes ci-dessus dans:

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

Je vais donc emprunter une partie de la réponse de @ dogbane pour cela, car cette ligne sed pour supprimer les lignes vides en tête est si courte ...

tac fait partie de coreutils et inverse un fichier. Alors faites-le deux fois:

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

Ce n'est certainement pas le plus efficace, mais à moins que vous ayez besoin efficacité, je le trouve plus lisible que tout le reste jusqu'à présent.

10
Izkata

voici une solution en un seul passage dans awk: elle ne commence pas à imprimer avant de voir une ligne non vide et lorsqu'elle voit une ligne vide, elle s'en 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

Notez qu'en raison du mécanisme que j'utilise pour prendre en compte les lignes vides/non vides (avec [[:graph:]] et /^[[:space:]]*$/), les lignes intérieures comportant uniquement des espaces seront tronquées pour devenir réellement vides.

3
glenn jackman

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
2
Kent

Comme mentionné dans une autre réponse , tac fait partie de coreutils , et inverse un fichier. En combinant l’idée de le faire deux fois avec le fait que la substitution de commande supprime les dernières lignes , nous obtenons

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

qui ne dépend pas de sed. Vous pouvez utiliser echo -n pour supprimer le retour à la ligne restant.

2
Jason Gross

Voici une version sed adaptée, qui considère également "vide" les lignes contenant uniquement des espaces et des tabulations.

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

Il s’agit essentiellement de la version de réponse acceptée (en tenant compte du commentaire de BryanH), mais le point . de la première commande a été remplacé par [^[:blank:]] (tout ce qui n’est pas vide) et le \n de la deuxième adresse de commande a été remplacé par [[:space:]] pour autoriser les nouvelles lignes, les espaces et les tabulations.

Une version alternative, sans utiliser les classes POSIX, mais votre sed doit prendre en charge l’insertion de \t et de \n dans […]. GNU sed, BSD sed pas.

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

Essai:

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

Pour une version non récursive efficace de la bande de fin de ligne de fin (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 pour stocker toutes les lignes vierges et ne les imprime que lorsqu'il trouve une ligne non vierge. Si quelqu'un ne veut que les nouvelles lignes, il suffit de supprimer les deux parties [[:space:]]*:

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

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

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

sur un fichier de 3 Mo avec 1 Mo de lignes vierges 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 en streaming prenait environ 0,5 seconde, la procédure récursive ne se terminant pas après 15 minutes. Gagner :)

Par souci d’exhaustivité, les lignes principales qui enlèvent le script sed sont déjà bien diffusées. Utilisez le plus approprié pour vous.

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

En bash, en utilisant chat, wc, grep, sed, queue et 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

Man, ça vaut vraiment la peine d'apprendre le "vrai" langage de programmation pour éviter cette laideur!

1

Utiliser bash

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

Je voudrais introduire 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
0
puchu

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ées. Au lieu de cela, nous attendons jusqu'à ce que nous obtenions une ligne non vide, et seulement à ce moment-là, nous renvoyons d'abord autant de lignes vides que précédemment, et ensuite seulement nous renvoyons à la nouvelle ligne non vide.

0
Adi Degani

Abash solution .

Remarque: Seulement utile si le fichier est assez petit pour être lu en mémoire en une fois.

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file) lit l'intégralité du fichier et ajuste fin nouvelles lignes, car la substitution de commande ($(....)) implicitement le fait.
  • =~ est l'opérateur opérateur de correspondance d'expression régulière de bash, et =~ ^$'\n'*(.*)$ correspond éventuellement à n'importe quel interligne newlines (avec avidité) et capture tout ce qui vient après. Notez le $'\n' potentiellement déroutant, qui insère une nouvelle ligne littérale à l’aide de ANSI C quoting , car la séquence d’échappement \n n’est pas prise en charge.
  • Notez que cette expression rationnelle toujours correspond, donc la commande après && est toujours exécutée.
  • La variable de tableau spéciale BASH_REMATCH rematch contient les résultats de la dernière correspondance regex et l'élément de tableau [1] contient ce que la (première et unique) sous-expression entre parenthèses (groupe de capture) capturée est la chaîne d'entrée avec les nouvelles lignes principales supprimées. L'effet net est que ${BASH_REMATCH[1]} contient le contenu du fichier d'entrée avec les nouvelles lignes principales et les nouvelles lignes supprimées.
  • Notez que l'impression avec echo ajoute une nouvelle ligne de fin. Si vous voulez éviter cela, utilisez plutôt echo -n (ou utilisez le plus portable printf '%s').
0
mklement0

@dogbane a une réponse simple et sympa pour supprimer les principales lignes vides. Voici une simple commande awk qui supprime uniquement les lignes de fin. Utilisez ceci avec la commande sed de @ dogbane pour supprimer les espaces en début et en fin.

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

C'est assez simple en opération. 

  • Ajouter chaque ligne à un tampon comme nous le lisons. 
  • Pour chaque ligne contenant un caractère, imprimez le contenu de la mémoire tampon, puis effacez-le.

Ainsi, les seules choses qui sont mises en mémoire tampon et jamais affichées sont les espaces vides.

J'ai utilisé printf au lieu de print pour éviter l'ajout automatique d'une nouvelle ligne, car j'utilise déjà les nouvelles lignes pour séparer les lignes du tampon.

0
Andy Mortimer