web-dev-qa-db-fra.com

Comment remplacer rapidement un paramètre dans une chaîne de commandes canalisée

Question

Disons que je viens de saisir cette commande pour obtenir le nombre de lignes contenant une chaîne particulière:

me@machine $ command-producing-multi-line-output | grep -i "needle" | wc -l

Maintenant, comment pourrais-je remplacer rapidement "needle" avec un autre mot?


Solution inefficace actuelle

En ce moment je:

  1. Appuyez sur Up sur le clavier pour charger la dernière commande.
  2. Appuyez sur Left ou Ctrl + Left jusqu'à ce que j'atteigne "needle".
  3. Appuyez sur Backspace ou Ctrl + w pour supprimer le mot.
  4. Tapez ou collez le nouveau mot.
  5. Appuyez sur Enter.

Cela semble assez inefficace.


Tentatives de solutions non fonctionnelles

J'ai essayé de rechercher des raccourcis historiques comme !!:sg/needle/new-needle; mais cela pose quelques problèmes:

  1. Vous ne pouvez pas appuyer sur Up pour mettre !!:sg/needle/new-needle de retour sur la ligne de commande. Faire cela montrera simplement à quoi il se développe (en revenant directement au problème d'origine).
  2. En répétant cela avec une nouvelle aiguille, vous devez remplacer à la fois needle et new-needle (c'est à dire. !!:sg/new-needle-from-before/yet-another-new-needle).
  3. Vous devez saisir l'intégralité de needle au lieu d'utiliser quelque chose comme !:4 ou $4 (c'est à dire. !!:sg/!:4/new-needle ou !!:sg/$4/new-needle) pour gagner du temps et des frappes, quelle que soit la longueur de l'aiguille.

J'ai aussi trouvé des choses comme !:^-3 "new-needle" !:5-$ mais qui a aussi des problèmes:

  1. Il se développe dans l'histoire et ne peut donc pas être réutilisé rapidement.
  2. Même s'il ne s'est pas développé, vous rencontrez le problème d'origine de devoir remplacer un mot au milieu d'une chaîne de commande.

Je crois qu'il doit y avoir un moyen super rapide de faire ce que je veux faire, et que certains gourous de Linux le savent. Je serais très reconnaissant pour toute contribution ou suggestion à ce sujet.


ÉDITER:

Contexte

Juste un peu de contexte, je travaille beaucoup avec OpenStack sur la ligne de commande et je me retrouve souvent devant remplacer un paramètre à plusieurs endroits dans une longue chaîne de commandes de commandes piped.

La façon dont nos environnements OpenStack sont configurés, plusieurs ingénieurs partagent un seul utilisateur stack sur un nombre illimité de serveurs, et il existe plusieurs clusters dans plusieurs environnements. Donc, une fonction déclarée dans .bashrc ou .profile le fichier n'est pas vraiment possible.

J'espère quelque chose de portable qui peut être utilisé rapidement avec une configuration nulle ou très minimale requise. Sinon, il se peut que je doive simplement recourir à une solution entièrement extérieure au shell (comme le remplacement du presse-papiers dans AutoHotKey/Keyboard Maestro/AutoKey, etc.).

11
Jeff Reeves

J'ai rencontré une situation similaire et présente ma solution ici au cas où ce serait un modèle utile.

Une fois que je me rends compte que je modifie à plusieurs reprises une donnée ennuyeuse à remplacer de manière interactive, je m'arrête et j'écris une petite boucle while:

$ command-producing-multi-line-output | grep -i "needle" | wc -l
0
$ command-producing-multi-line-output | grep -i "haystack" | wc -l
0
$ while read needle; do

Up-Arrow à la précédente command-producing-multi-line-output ligne et remplacer "haystack" avec "$needle"

command-producing-multi-line-output | grep -i "$needle" | wc -l; done
something
0
something else
0
gold
1

(se terminant par Control+D)

Ou la variation légèrement plus fantaisiste:

$ while read needle; do
printf 'For: %s\n' "$needle"
command-producing-multi-line-output | grep -i "$needle" | wc -l; done

Si je vois un avenir pour cette ligne de commande au-delà de "aujourd'hui", je l'écrirai dans une fonction ou un script.

10
Jeff Schaller

C'est exactement à quoi sert expansion historique :

$ command-producing-multi-line-output | grep -i "needle" | wc -l
...
$ ^needle^nipple
command-producing-multi-line-output | grep -i "nipple" | wc -l
...
11
mosvy

Un alias utile dans bash est

alias r='fc -s'

La commande r se trouve souvent dans d'autres shells par défaut1et répète la commande la plus récente de l'histoire. Le manuel bash y fait même référence comme un alias utile pour définir:

[...]  A useful alias to use with this is ``r="fc -s"'',
so that typing ``r cc'' runs the last command beginning with
``cc'' and typing ``r'' re-executes the last command.

Il vous permettra également d'effectuer des remplacements dans le texte de la dernière commande.

Ici, j'exécute votre commande, puis j'utilise l'alias ci-dessus pour remplacer le mot needle par le mot haystack:

$ command-producing-multi-line-output | grep -i "needle" | wc -l
bash: command-producing-multi-line-output: command not found
       0
$ r needle=haystack
command-producing-multi-line-output | grep -i "haystack" | wc -l
bash: command-producing-multi-line-output: command not found
       0

Lorsque j'utilise r needle=haystack sur la ligne de commande, le Shell imprime la commande qu'il va exécuter puis l'exécute immédiatement. Comme vous pouvez le voir, il remplace également la Parole.

Les erreurs sont évidemment dues au fait que je n'ai pas de command-producing-multi-line-output, mais ce n'est pas important dans cet exercice.


La commande fc ne sera pas enregistrée dans votre historique, mais vous pouvez la faire enregistrer en la créant comme une fonction Shell comme ceci:

fc() {
    command fc "$@"
    history -s fc "$@"   # append the given fc command to history
}

Vous pouvez alors faire

$ command-producing-multi-line-output | grep -i "needle" | wc -l
bash: command-producing-multi-line-output: command not found
       0

Réexécutez la commande la plus récente commençant par comm et remplacez needle à l'aide de r needle=haystack comm:

$ r needle=haystack comm
command-producing-multi-line-output | grep -i "haystack" | wc -l
bash: command-producing-multi-line-output: command not found
       0

Réexécutez la commande la plus récente, mais remplacez haystack, à l'aide de r haystack=beeswax:

$ r haystack=beeswax
fc -s needle=beeswax comm
command-producing-multi-line-output | grep -i "beeswax" | wc -l
bash: command-producing-multi-line-output: command not found
       0

Notez le double appel qui est effectué vers fc dans cette dernière ligne, d'abord via notre alias r puis via notre fonction fc.

De toute évidence, l'enregistrement de la commande fc dans l'historique vous fait courir le risque d'appeler accidentellement fc -s récursivement.


1Dans zsh c'est une commande intégrée, dans OpenBSD ksh c'est un alias par défaut pour fc -s, dans ksh93 c'est un alias par défaut pour hist -s, etc.

8
Kusalananda

L'OP indique qu'une fonction déclarée dans .bashrc n'est pas pratique, mais rien n'empêche d'en déclarer une de manière interactive. Comme il s'agit uniquement de la session en cours, un nom de lettre unique convient, par ex. q.

Commençant par

$ command-producing-multi-line-output | grep -i "haystack" | wc -l

presse Up;} pour terminer une fonction, modifiez le haystack en $1Control+aq(){Space obtenir

q(){ command-producing-multi-line-output | grep -i "$1" | wc -l;}

alors vous pouvez utiliser q needle, q haystack, q something. Je préfère cela à la solution while dans la réponse actuellement acceptée car elle permet d'autres commandes entre la recherche/le comptage.

Pour ce cas particulier, je définirais en fait

q(){ command-producing-multi-line-output | grep -i "$@";}

comme je voudrais probablement voir les lignes qui correspondent réellement à un moment donné, donc je pourrais utiliser q needle | wc -l ou mieux q -c needle pour compter et q needle pour voir les lignes.

Comme la définition de la fonction va être stockée dans l'historique bash, il est généralement possible de recharger la définition si elle est nécessaire dans une prochaine session.

5
icarus

Sommaire: Ctrl+R nee Alt+D haystack Enter

Presse Ctrl+R pour démarrer une inversion recherche incrémentale et tapez quelques caractères du texte que vous souhaitez remplacer (ou du texte très proche) jusqu'à ce que la partie souhaitée de la ligne de commande souhaitée apparaisse (elle n'a pas être la ligne de commande précédente). presse Ctrl+R pour rechercher l'occurrence précédente. presse Ctrl+S d'aller de l'avant si vous êtes allé trop loin. presse Backspace si vous avez mal saisi un personnage.

Une fois que vous avez atteint l'endroit souhaité, vous pouvez utiliser des touches comme DeleteAlt+D ou Alt+Backspace pour commencer à supprimer du texte autour du curseur, ou tout simplement Escape pour quitter la recherche incrémentielle au point que vous avez atteint.

Presse Enter uniquement lorsque vous avez terminé de modifier la ligne de commande. Notez que Enter lors d'une recherche incrémentielle a sa signification normale pour exécuter la ligne de commande actuelle telle qu'elle est: elle ne se contente pas de quitter le mode de recherche (appuyez sur Escape pour simplement quitter le mode de recherche).

Dans ces situations, j'utilise souvent une variable d'environnement pour le paramètre comme suit:

ARG1=needle; command-producing-multi-line-output | grep -i "${ARG1}" | wc -l

La première affectation définit une variable d'environnement qui peut ensuite être utilisée dans les commandes suivantes. Cela sert de paramètre qui peut être modifié selon les besoins. Notez que cela définira une variable d'environnement pour la durée de vie de ce shell, mais j'ai trouvé que pour les commandes rapides et spécifiques, c'est correct. Les règles habituelles s'appliquent à l'expansion des variables, comme les guillemets simples '$ {ARG1}' ne développeront pas la variable. Des informations générales peuvent être trouvées à ce sujet (en voici une) https://www.gnu.org/software/bash/manual/html_node/Quoting.html

0
Paul

Je me retrouve à utiliser fzf pour cela.

Si vous êtes prêt à installer quelque chose comme fzf et que votre sortie est normalement inférieure à un écran, essayez ceci:

echo | fzf -q 'shuf < /usr/share/dict/words | grep needle | head' --preview-window=up:99% --preview="eval {q}"

Une fois la sortie affichée, placez le curseur à la fin du mot "aiguille", appuyez sur Ctrl-W et tapez botte de foin. Ensuite, appuyez à nouveau sur Ctrl-W et tapez un autre mot. Etc.

Cependant, je ne conseillerais pas cela pour les commandes qui changent le système :-) Vous utilisez la fonction d'aperçu de fzf pour exécuter la commande, et fzf essaie de l'exécuter dès que quelque chose change. Par exemple, lorsque vous appuyez sur Ctrl-W, vous verrez grep montrant son message d'utilisation car momentanément, jusqu'à ce que vous tapiez le premier caractère du mot de remplacement, il n'y a aucun argument.

J'utilise cela si souvent que j'ai un script appelé try, qui est fondamentalement le suivant:

#!/bin/bash

export FZF_DEFAULT_COMMAND=echo
fzf -q "$*" --preview-window=up:99% --preview="eval {q}"
0
sitaram

Ouvrez un éditeur approprié qui est construit pour ce genre de choses. Par défaut, vous pouvez le faire avec Ctrl-xCtrl-e, cependant, si vous avez activé le mode d'édition vi (une façon est set -o vi), vous pouvez aussi utiliser Escv.

Une fois sur place, utilisez la commande de substitution de votre éditeur. Dans vi (m), par exemple,

:%substitute/needle/haystack/g
0
D. Ben Knoble