web-dev-qa-db-fra.com

Coupez les 3 derniers caractères d'une ligne SANS utiliser sed, ou Perl, etc.

J'ai un script Shell produisant des données comme ceci:

1234567890  *
1234567891  *

Je dois supprimer JUSTE les trois derniers caractères "*". Je sais que je peux le faire via

(whatever) | sed 's/\(.*\).../\1/'

Mais je ne veux pas utiliser sed pour des raisons de vitesse. Ce seront toujours les mêmes 3 derniers caractères.

Un moyen rapide de nettoyer la sortie?

38
RubiCon10

En supposant que toutes les données soient formatées comme dans votre exemple, utilisez ' cut ' pour obtenir la première colonne uniquement.

cat $file | cut -d ' ' -f 1  

ou pour obtenir les 10 premiers caractères.

cat $file | cut -c 1-10
12
Larry Wang

Voici un tour Unix à l'ancienne pour supprimer les 3 derniers caractères d'une ligne qui n'utilise pas sed OR awk ...

> echo 987654321 | rev | cut -c 4- | rev

987654

Contrairement à l'exemple précédent utilisant "cut", cela ne nécessite pas de connaître la longueur de la ligne.

166
sitzen2k

Je peux vous garantir que bash ne sera pas plus rapide que sed pour cette tâche. Démarrer des processus externes dans bash est généralement une mauvaise idée, mais seulement si vous le faites souvent.

Donc, si vous démarrez un processus sed pour chaque ligne de votre entrée, je serais inquiet. Mais tu n'est pas. Il vous suffit de commencer un sed qui fera tout le travail à votre place.

Vous pouvez cependant constater que le sed suivant sera un peu plus rapide que votre version:

(whatever) | sed 's/...$//'

Cela ne fait que supprimer les trois derniers caractères de chaque ligne, plutôt que de remplacer la ligne entière par une version plus courte d'elle-même. Maintenant, peut-être que des moteurs RE plus modernes peuvent optimiser votre commande, mais pourquoi prendre le risque?.

Pour être honnête, la seule façon dont je pourrais penser que ce serait plus rapide serait de créer à la main votre propre programme de filtrage basé sur le langage C. Et la seule raison pour laquelle peut être plus rapide que sed est que vous pouvez tirer parti des connaissances supplémentaires que vous avez sur vos besoins en matière de traitement ( sed doit permettre une procession généralisée, donc peut-être plus lent à cause de cela).

N'oubliez pas le mantra de l'optimisation: "Mesurer, ne devinez pas!"


Si vous voulez vraiment faire ceci ligne par ligne dans bash (et je maintiens toujours que c'est une mauvaise idée), vous peut utiliser:

pax> line=123456789abc
pax> line2=${line%%???}
pax> echo ${line2}
123456789
pax> _

Vous voudrez peut-être également vérifier si vous avez réellement besoin d’une amélioration de la vitesse. Si vous traitez les lignes comme un gros morceau, vous verrez que sed est très rapide. Tapez ce qui suit:

#!/usr/bin/bash

echo This is a pretty chunky line with three bad characters at the end.XXX >qq1
for i in 4 16 64 256 1024 4096 16384 65536 ; do
    cat qq1 qq1 >qq2
    cat qq2 qq2 >qq1
done

head -20000l qq1 >qq2
wc -l qq2

date
time sed 's/...$//' qq2 >qq1
date
head -3l qq1

et l'exécuter. Voici la sortie sur mon ordinateur portable R40 (pas très rapide du tout):

pax> ./chk.sh
20000 qq2
Sat Jul 24 13:09:15 WAST 2010

real    0m0.851s
user    0m0.781s
sys     0m0.050s
Sat Jul 24 13:09:16 WAST 2010
This is a pretty chunky line with three bad characters at the end.
This is a pretty chunky line with three bad characters at the end.
This is a pretty chunky line with three bad characters at the end.

Cela représente 20 000 lignes en moins d'une seconde, ce qui est plutôt bien pour une tâche effectuée toutes les heures.

30
paxdiablo
$ x="can_haz"
$ echo "${x%???}"
can_
11
A T

awk et sed sont tous deux très rapides, mais si vous pensez que c'est important, n'hésitez pas à utiliser l'un des éléments suivants:

Si les caractères que vous souhaitez supprimer sont toujours à la fin de la chaîne

echo '1234567890  *' | tr -d ' *'

S'ils peuvent apparaître n'importe où dans la chaîne et que vous voulez seulement les supprimer à la fin

echo '1234567890  *' | rev | cut -c 4- | rev

Les pages de manuel de toutes les commandes expliqueront ce qui se passe.

Je pense que vous devriez utiliser sed, cependant.

5
majhool

Tu pourrais essayer

(whatever) | while read line; do echo $line | head --bytes -3; done;

head devrait lui-même être plus rapide que sed ou cut car il n'y a pas de correspondance de regex ou de delimètre, mais appeler une pour chaque ligne séparément l'emporterait probablement sur celui.

2
Aaron J Lang

Note: Cette réponse est un peu une plaisanterie, mais ça marche vraiment ...

#!/bin/bash
outfile="/tmp/$RANDOM"
cfile="$outfile.c"
echo '#include <stdio.h>
int main(void){int e=1;char c;while((c=getc(stdin))!=-1){if(c==10)e=1;if(c==32)e=0;if(e)putc(c,stdout);}}' >> "$cfile"
gcc -o "$outfile" "$cfile"
rm "$cfile"
cat somedata.txt | "$outfile"
rm "$outfile"

Vous pouvez remplacer cat somedata.txt avec une commande différente.

2
icktoofay

Si le script génère toujours des lignes de 10 caractères suivis de 3 caractères supplémentaires (autrement dit, vous ne voulez que les 10 premiers caractères), vous pouvez utiliser

script | cut -c 1-10

Si elle génère un nombre incertain de caractères non-espace, suivis d'un espace et de 2 autres caractères supplémentaires (en d'autres termes, vous ne voulez que le premier champ), vous pouvez utiliser

script | cut -d ' ' -f 1

... comme dans le commentaire de majhool plus tôt. Selon votre plate-forme, vous pouvez également avoir colrm, ce qui, une fois de plus, fonctionnerait si les lignes avaient une longueur fixe:

script | colrm 11
1
Zac Thompson

Une autre réponse repose sur le dernier caractère étant un espace. Cela fonctionnera avec (presque) n'importe quel personnage dans cette position et le fera "SANS utiliser sed, ou Perl, etc.":

while read -r line
do
    echo ${line:0:${#line}-3}
done

Si vos lignes sont de longueur fixe, remplacez echo par:

echo ${line:0:9}

ou

printf "%.10s\n" "$line"

mais chacun de ceux-ci est certainement beaucoup plus lent que sed.

1
Dennis Williamson

Pas besoin de couper ou de magie, en bash vous pouvez couper une ficelle comme ceci:

  ORGSTRING="123456"
  CUTSTRING=${ORGSTRING:0:-3}
  echo "The original string: $ORGSTRING"
  echo "The new, shorter and faster string: $CUTSTRING"

Voir http://tldp.org/LDP/abs/html/string-manipulation.html

1
DusteD

Vous pouvez utiliser awk juste pour imprimer le premier "champ" s'il n'y a pas d'espaces (ou s'il y en aura, changez le séparateur ".

J'ai mis les champs que vous aviez ci-dessus dans un fichier et je l'ai fait

awk '{ print $1 }' < test.txt 
1234567890
1234567891

Je ne sais pas si c'est mieux.

0
Shawn D.

que voulez-vous dire ne veulent pas utiliser sed/awk à des fins de vitesse? sed/awk sont plus rapides que les boucles en lecture du shell pour le traitement des fichiers.

$ sed 's/[ \t]*\*$//' file
1234567890
1234567891

$ sed 's/..\*$//' file
1234567890
1234567891

avec bash shell

while read -r a b
do
 echo $a
done <file
0
ghostdog74