bash: moyen le plus court pour obtenir la n-ième colonne de sortie

166

Disons que pendant votre journée de travail, vous rencontrez à plusieurs reprises la forme suivante de sortie en colonnes à partir d'une commande dans bash (dans mon cas, lors de l'exécution svn stdans mon répertoire de travail Rails):

?       changes.patch
M       app/models/superman.rb
A       app/models/superwoman.rb

afin de travailler avec la sortie de votre commande - dans ce cas, les noms de fichiers - une sorte d'analyse est nécessaire pour que la deuxième colonne puisse être utilisée comme entrée pour la commande suivante.

Ce que j'ai fait est d'utiliser awkpour accéder à la deuxième colonne, par exemple lorsque je veux supprimer tous les fichiers (pas que ce soit un cas d'utilisation typique :), je ferais:

svn st | awk '{print $2}' | xargs rm

Puisque je tape souvent ceci, une question naturelle est: y a-t-il un moyen plus court (donc plus cool) d'accomplir cela dans bash?

REMARQUE: Ce que je demande est essentiellement une question de commande shell même si mon exemple concret est sur mon flux de travail svn. Si vous pensez que le flux de travail est idiot et que vous suggérez une approche alternative, je ne vous voterai probablement pas, mais d'autres pourraient le faire, car la question ici est vraiment de savoir comment obtenir la sortie de la commande de la n-ième colonne dans bash, de la manière la plus courte possible . Merci :)

Sv1
la source
1
Lorsque vous utilisez souvent une commande, il est préférable de créer un script et de le placer dans votre chemin. Vous pouvez simplement créer une fonction dans votre bashrc si vous préférez. Je ne vois pas l'intérêt de réduire l'expression de sélection de colonne.
Lynch le
1
Vous avez raison, et je pourrais le faire. Le `` point '' est la quête de nouvelles façons de faire des choses dans bash, à des fins d'apprentissage mais surtout pour le plaisir :)
Sv1
1
De plus, vous n'avez pas votre .bashrc lorsque vous ssh-ing quelque part, il est donc utile de vous y retrouver sans lui.
Kos

Réponses:

125

Vous pouvez utiliser cutpour accéder au deuxième champ:

cut -f2

Edit: Désolé, je n'ai pas réalisé que SVN n'utilise pas d'onglets dans sa sortie, donc c'est un peu inutile. Vous pouvez personnaliser cutla sortie, mais c'est un peu fragile - quelque chose comme cut -c 10- cela fonctionnerait, mais la valeur exacte dépendra de votre configuration.

Une autre option est quelque chose comme: sed 's/.\s\+//'

porges
la source
1
Essayez d'utiliser cut -f2avec la svn stsortie. Vous verrez que cela ne fonctionne pas.
Lynch le
J'ai supposé que le réel svn stutiliserait des onglets dans sa sortie. Après quelques tests, il semble que ce ne soit pas le cas. Zut.
porges le
21
Passer un délimiteur approprié pour -dpermettre à cela de fonctionner.
Yogh le
6
Pour développer ce que @Yogh a dit, pour les espaces comme délimiteur, cela ressemblerait à cut -d" " -f2.
adamyonk
Sans awk sur stdout, utilisez xargs: svn st | xargs | cut -d "" -f2
vr286
106

Pour accomplir la même chose que:

svn st | awk '{print $2}' | xargs rm

en utilisant uniquement bash, vous pouvez utiliser:

svn st | while read a b; do rm "$b"; done

Certes, ce n'est pas plus court, mais c'est un peu plus efficace et il gère correctement les espaces dans vos noms de fichiers.

Rick Sladkey
la source
Qu'est-ce que aet bcomment les obtenir?
Timo du
1
@Timo areprésente la première colonne et breprésente les colonnes restantes. Si vous souhaitez imprimer la deuxième colonne, utilisez read a b c;puis utilisez à la echoplace de rm. J'ai utilisé cela pour obtenir un tas de processus d'identifiants qui suivaient un modèle grep, afin que je puisse tous les interrompre.
Matt Kleinsmith
37

Je me suis retrouvé dans la même situation et j'ai fini par ajouter ces alias à mon .profilefichier:

alias c1="awk '{print \$1}'"
alias c2="awk '{print \$2}'"
alias c3="awk '{print \$3}'"
alias c4="awk '{print \$4}'"
alias c5="awk '{print \$5}'"
alias c6="awk '{print \$6}'"
alias c7="awk '{print \$7}'"
alias c8="awk '{print \$8}'"
alias c9="awk '{print \$9}'"

Ce qui me permet d'écrire des choses comme ça:

svn st | c2 | xargs rm
EmpiléCrooked
la source
15
Les fonctions Bash sont généralement plus utiles. J'ai fait ceci: function c() { awk "{print \$$1}" } Ensuite, vous pouvez faire: svn st | c 2 | xargs rm
Aissen
8
Mais ensuite, j'ai besoin de taper un espace supplémentaire. C'est trop épuisant :)
StackedCrooked
16

Essayez le zsh. Il prend en charge les alias de suffixe, vous pouvez donc définir X dans votre .zshrc pour être

alias -g X="| cut -d' ' -f2"

alors vous pouvez faire:

cat file X

Vous pouvez aller plus loin et le définir pour la nième colonne:

alias -g X2="| cut -d' ' -f2"
alias -g X1="| cut -d' ' -f1"
alias -g X3="| cut -d' ' -f3"

qui affichera la nième colonne du fichier "file". Vous pouvez également le faire pour une sortie grep ou une sortie inférieure. Ceci est très pratique et une fonctionnalité tueur du zsh.

Vous pouvez aller plus loin et définir D comme:

alias -g D="|xargs rm"

Vous pouvez maintenant taper:

cat file X1 D

pour supprimer tous les fichiers mentionnés dans la première colonne du fichier "fichier".

Si vous connaissez le bash, le zsh n'est pas vraiment un changement, à l'exception de quelques nouvelles fonctionnalités.

HTH Chris

Chris
la source
ahh, je vois que vous avez mis à jour votre réponse pendant que je tapais mon commentaire ci-dessus :) Pouvez-vous spécifier dynamiquement quelle colonne obtenir, ou aurais-je besoin de n-lignes dans mon .zshrc (pas que ce soit important, juste curieux)
Sv1
J'ai encore édité mon message et défini un suffixe «D» pour supprimer les fichiers. Autant que je sache, vous devrez ajouter une ligne pour chaque suffixe.
Chris
Ou faites-en le premier paramètre de la commande X. Rien de tout cela ne nécessite zsh ou alias, sauf peut-être l'idée particulière de mettre un tube dans un alias.
tripleee
1
Je ne comprends pas pourquoi vous semblez tous aimer cette cutoption. Cela ne fonctionne tout simplement pas sur ma machine en utilisant la sortie de svn st. Essayez svn st | cut -d' ' -f2juste de voir ce qui se passe.
Lynch le
@ Sv1 vous pouvez écrire une boucle pour définir les alias, car l'alias n'est qu'une commande.
driftcatcher
8

Parce que vous semblez ne pas être familier avec les scripts, voici un exemple.

#!/bin/sh
# usage: svn st | x 2 | xargs rm
col=$1
shift
awk -v col="$col" '{print $col}' "${@--}"

Si vous enregistrez ceci dans ~/bin/xet assurez-vous qu'il ~/binest dans votre PATH(maintenant c'est quelque chose que vous pouvez et devriez mettre dans votre .bashrc), vous avez la commande la plus courte possible pour extraire généralement la colonne n; x n.

Le script doit effectuer une vérification des erreurs et une caution appropriées s'il est appelé avec un argument non numérique ou un nombre incorrect d'arguments, etc. mais développer cette version essentielle nue se fera dans l'unité 102.

Vous voudrez peut-être étendre le script pour autoriser un séparateur de colonne différent. Awk par défaut analyse l'entrée dans les champs sur les espaces; pour utiliser un autre délimiteur, utilisez -F ':'where :est le nouveau délimiteur. L'implémenter en tant qu'option du script le rend légèrement plus long, donc je laisse cela comme un exercice pour le lecteur.


Usage

Étant donné un fichier file:

1 2 3
4 5 6

Vous pouvez soit le passer via stdin (en utilisant simplement un espace réservé inutilecat pour quelque chose de plus utile);

$ cat file | sh script.sh 2
2
5

Ou fournissez-le comme argument du script:

$ sh script.sh 2 file
2
5

Ici, sh script.shsuppose que le script est enregistré comme script.shdans le répertoire courant; si vous l'enregistrez avec un nom plus utile quelque part dans votre PATHet que vous le marquez comme exécutable, comme dans les instructions ci-dessus, utilisez évidemment le nom utile à la place (et non sh).

tripleee
la source
Bonne idée, mais ne fonctionne pas tout à fait pour moi comme sur sh ou bash. Cela fonctionne: #! / Bin / bash col = $ 1 shift awk "{print \ $$ col}"
mgk
Merci pour ce commentaire tardif. Mis à jour avec votre correctif.
tripleee
1
mieuxawk -v col=$col ...
fedorqui 'Alors arrêtez de nuire'
@fedorqui Merci pour vos commentaires, ici et ailleurs! Mise à jour de la réponse. C'est maintenant un peu plus long, donc si vous voulez le script le plus court possible, consultez l' historique des révisions de la version originale.
tripleee
1
@fedorqui Merci beaucoup! Ajout d'une petite mise en garde à l' catexemple pour des raisons hystériques (-:
tripleee
5

Il semble que vous ayez déjà une solution. Pour faciliter les choses, pourquoi ne pas simplement mettre votre commande dans un script bash (avec un nom court) et l'exécuter au lieu de taper cette commande `` longue '' à chaque fois?

Tarek Fadel
la source
C'est juste que ce n'est pas aussi «cool» à mon avis. Pourquoi? Eh bien, je ne veux pas avoir à inonder mon .bashrc avec divers raccourcis qui font ceci et cela, essentiellement en écrivant un méta-shell. C'est assez aliasé comme ça. Cela dit, votre suggestion n'est pas mauvaise.
Sv1
2
.bashrc? Pourquoi voudriez-vous l'encombrer de trucs non liés au bash. Ce que je fais est d'écrire des scripts individuels pour chaque «ensemble» de commandes et de les mettre tous dans un ~/scriptsrépertoire. Ensuite, ajoutez le tout ~/scriptsà mon PATHpour que je puisse simplement les appeler par leur nom lorsque j'en ai besoin.
Tarek Fadel le
4
Alors ne le mettez pas dans votre .bashrc. Créez un script dans ~ / bin et assurez-vous qu'il se trouve sur votre PATH.
tripleee le
2

Si vous êtes d'accord avec la sélection manuelle de la colonne, vous pouvez être très rapide en utilisant pick :

svn st | pick | xargs rm

Allez simplement dans n'importe quelle cellule de la 2ème colonne, appuyez sur cpuis appuyez surenter

Bernardo Rufino
la source
J'ai essayé l'outil "pick" que vous avez référencé, et je l'aime beaucoup. C'est très bien pour beaucoup de situations où je veux imprimer les colonnes sélectionnées mais ne veux pas taper "awk '{print $ 3}'" juste pour obtenir la colonne désirée. Malheureusement, le nom entre en conflit avec un autre outil "pick" qui semble être plus populaire - il peut être installé avec "apt install pick". J'ai donc renommé votre outil en "pickk" sur mon système et j'ai l'intention de continuer à l'utiliser. Merci d'avoir publié une référence.
William Dye
1

Notez que ce chemin d'accès au fichier ne doit pas nécessairement être dans la deuxième colonne de la sortie svn st. Par exemple, si vous modifiez le fichier et modifiez sa propriété, ce sera la 3e colonne.

Voir les exemples de sortie possibles dans:

svn help st

Exemple de sortie:

 M     wc/bar.c
A  +   wc/qax.c

Je suggère de couper les 8 premiers caractères par:

svn st | cut -c8- | while read FILE; do echo whatever with "$FILE"; done

Si vous voulez être sûr à 100% et gérer les noms de fichiers fantaisistes avec un espace blanc à la fin par exemple, vous devez analyser la sortie xml:

svn st --xml | grep -o 'path=".*"' | sed 's/^path="//; s/"$//'

Bien sûr, vous voudrez peut-être utiliser un véritable analyseur XML au lieu de grep / sed.

Michał Šrajer
la source