Que dois-je utiliser lorsque couper ne le coupe pas?

19

J'ai un fichier citiescomme celui-ci:

[1598] San Diego, US (inactive)
[4517] St Louis, US (inactive)
[6346] Orlando, US (inactive)

Je veux découper les noms des villes pour avoir:

San Diego
St Louis
Orlando

C'est le mieux que j'ai pu trouver:

cut -d ',' -f1 cities | cut -d ']' -f2

Mais cela me laisse encore un espace devant les noms. Existe-t-il une cutcommande similaire que je peux utiliser qui accepte les délimiteurs de plusieurs caractères afin que je puisse continuer ]?

Kit Sunde
la source
1
trest utile pour supprimer les caractères dont vous ne voulez pas.
LawrenceC
Si vous essayez le code dans les réponses des gens, vous verrez trois sorties différentes. Cela suggère que votre question n'était pas claire à 100%. Est-ce que «couper» signifie supprimer ou sélectionner? Voulez-vous le (inactive)statut ou non? Veuillez fournir un exemple de sortie.
Mikel
@Mikel - Considérant que j'utilise cutpour couper les choses et que vous pouvez voir l'intention de l'exemple raté que j'ai, cela devrait être assez clair dans le contexte. Je vais cependant fournir un échantillon pour le clarifier davantage. :)
Kit Sunde
Non, pas vraiment. J'ai modifié une phrase de votre question pour "n'imprimer que les noms des villes", car c'est votre utilisation du mot "couper" qui n'était pas claire pour moi. Ma modification est-elle correcte?
Mikel
1
@Kit Sunde: Avec l'exemple de sortie, c'est certainement compréhensible. Le titre est mignon. "couper" me fait penser à ce qui se passe lorsque vous appuyez sur Ctrl + X, c'est pourquoi j'ai suggéré le changement, mais c'est votre question. Le vote en aval serait idiot quand ce n'est qu'un simple désaccord.
Mikel

Réponses:

15

Awk (vérifiez également Awk Info ) est magnifique avec ce genre de question. Essayer:

awk -F'[],] *' '{print $2}' cities

Cela définit un séparateur de champ -Fcomme [],] *- ce qui signifie une occurrence d'un crochet carré de fermeture ou d'une virgule, suivie de zéro ou d'un nombre quelconque d'espaces. Bien sûr, vous pouvez changer cela pour répondre à toutes les exigences. Lisez sur les expressions régulières.

Une fois la ligne divisée, vous pouvez faire ce que vous voulez avec le résultat de la division. Ici, j'ai décidé d'imprimer le deuxième champ uniquement avec print $2. Notez qu'il est important d'utiliser des guillemets simples autour des instructions awk sinon $ 2 est remplacé par le shell.

asoundmove
la source
2
]n'est pas un support d'angle. Les équerres sont <>. []sont des "crochets" ou simplement des "crochets".
cjm
Je pense que vous devez échapper à cette parenthèse fermante, sauf si j'ai vraiment besoin de lire mes expressions régulières.
Kit Sunde
@cjm - Peut-être qu'il est allemand: news.ycombinator.com/item?id=1181243 :)
Kit Sunde
1
@cjm, désolé, je voulais dire entre crochets, tapé un peu trop vite. @Kit, je ne suis pas allemand. Vous ne voulez pas échapper au crochet de fermeture interne (cela ne servirait à rien), mais ce doit être le premier caractère de la plage.
asoundmove
12

Vous pouvez modifier le dernier cutde votre pipeline comme suit:

cut -d ' ' -f2-

Ce qui précède signifie que le séparateur de champs est un espace, et nous voulons sélectionner tous les champs à partir du second. La séquence complète devient:

cut -d ',' -f1 cities | cut -d ' ' -f2-
Barun
la source
12

Pour une analyse plus complexe, vous devez utiliser sed (1) :

sed -e 's/\[[0-9]\+\] \([^,]\+\),.*/\1/' cities

Ou en utilisant -rpour simplifier l'expression régulière, comme le suggère pepoluan :

sed -re 's/\[[0-9]+\] ([^,]+),.*/\1/' cities
Juliano
la source
2
+1. vous pouvez également utiliser -r pour éviter de s'échapper des caractères regex avancés, simplifiant considérablement le modèle regex
pepoluan
0

J'utilise normalement Perl lorsque les choses deviennent trop difficiles pour sed et grep.

Il existe plusieurs façons de l'écrire en Perl. Par exemple, vous pouvez préférer qu'il soit rapide, ou vous pouvez préférer qu'il gère de légers problèmes inattendus dans l'entrée (par exemple, deux espaces où l'un était prévu).

Une façon évidente (suppose que l'id est numérique, la ville est alphabétique, le statut est alphabétique):

while (<>) {
    if (/^\[\d+\] (\w+(?: \w+)*), \w+ \(\w*\)$/) {
        my $city = $1;
        print "$city\n";
    }
}

Ou plus lent mais plus permissif (fait plus de recul):

while (<>) {
    if (/^.*\]\s+(.*),.*$/) {
        my $city = $1;
        print "$city\n";
    }
}

Ou plus rapidement (le champ s'arrête à la première occurrence du crochet de fermeture):

while (<>) {
    if (/^\[[^]]*\] ([^,]*), \S+ \([^)]*\)$/) {
        my $city = $1;
        print "$city\n";
    }
}

À partir de la ligne de commande plutôt que d'un script, vous pouvez utiliser l' -noption, qui ajoute essentiellement la while (<>) { BLOCK }boucle:

perl -ne '/^\[[^]]*\] ([^,]*), \S+ \([^)]*\)$/ and print $1, "\n";' cities

ou si vous voulez que l'utilisation ressemble à couper, vous pouvez utiliser l' -Foption, qui est similaire à l' -Foption awk , par exemple:

perl -a -n -F'/[],]\s+/' -e 'print $F[1], "\n"' cities

De cette façon, on suppose évidemment qu'aucun champ ne contiendra aucun des délimiteurs.

Mikel
la source