Remplacer la barre oblique inverse par une barre oblique dans la dernière commande

10

J'ai copié la commande cd C:\foo\bar\de PowerShell vers Cygwin et je m'attendais à ce qu'elle s'exécute. J'essaie maintenant d'exécuter une substitution pour remplacer tous les éléments \par /:

$ !!:gs/\\/\/
bash: :gs/\\/\/: substitution failed

Je ne sais pas pourquoi j'obtiens la substitution a échoué.

J'ai aussi essayé:

$ !!:gs/\\/q

Juste pour voir si le remplacement était le problème. Ce n'est pas. Maintenant je suis curieux!

Pourquoi est-ce que j'obtiens "la substitution a échoué"?

Tanner Faulkner
la source
2
Je n'ai pas de cygwin à portée de main en ce moment, mais je pensais que les chemins Windows fonctionnent ... avez-vous essayé de citer l'argument de façon singulière? Iecd 'C:\foo\bar'
mpy
@mpy Cela fonctionne aussi! Je ne savais pas que je n'étais pas coincé avec la substitution.
Tanner Faulkner
1
"Serait ouvert à une réponse utilisant également zsh". Votre original !!:gs/\\/\/fonctionne en zsh…
Kamil Maciorowski
@TannerFaulkner fc -s '\'='/' -1fonctionne sous le bash par défaut d'Ubuntu LTS. Faites-moi savoir si cela fonctionne aussi sous Cygwin. J'ai posté plus de mots dans la réponse.
Hastur
1
je pense que le comportement bash général ici est que les barres obliques inverses sont traitées comme des échappements avant que la substitution ne se produise !!:gs/\\/\//. @Hastur fc -s '\'='/' -1obtient le comportement attendu. cela pourrait être un bogue dans bash.
Don Quichotte

Réponses:

6

une barre oblique est également le délimiteur de votre commande de remplacement, vous devez donc l'échapper en tant que chaîne de remplacement afin de ne pas terminer la substitution plus tôt

!!:gs/\\/\//

ou plus simplement, comme suggéré dans les commentaires, en utilisant autre chose qu'une barre oblique comme délimiteur

!!:gs|\\|/|

Mais cela ne semble fonctionner que dans tcsh(le shell généralement assigné où je suis), je ne peux pas faire fonctionner la commande bashcar il semble que l'échappement ne fonctionne pas sur le premier argument de:s

infixé
la source
No joy :( Même erreur i.imgur.com/QFQR0xF.png
Tanner Faulkner
1
Juste une remarque: !!:gs|\\|/pourrait être un peu plus lisible.
mpy
1
Je ne pouvais pas le faire fonctionner en bash. Le premier argument de :sne semble pas être un vrai regex
infixé le
4

Réponse courte: la fonction intégrée fc(commande fix) de bash

fcest la commande, intégrée au shell bash, conçue pour éditer et réexécuter les commandes de l'historique.
Il est également présent sur CygWin et fonctionne sur toutes les distributions Linux sur lesquelles j'ai testé:

fc  -s '\'='/' -1

Quelques explications

  • fcest le bash intégré commande à la liste ou modifier et re-exécuter des commandes à partir de la liste d'historique.
  • -1prendra la dernière commande de l'histoire. Notez que even !!est défini (lu depuis man bash) comme

    !! Refer to the previous command. This is a synonym for! -1 '.

  • -spour remplacer un motif par un autre. Cette fois à partir de help fc(commande intégrée donc help):

    Avec le format `fc -s [pat = rep ...] [command] ', COMMAND est réexécuté après que la substitution OLD = NEW soit effectuée.

    Ok ils veulent dire pat=repau lieu de OLD=NEW...

  • Les motifs seront lus par le shell donc nous devons les protéger avec la citation: '\'et '/'.

Quelques mots de plus sur les raisons de l'échec de la substitution

Il semble que pour le s modificateur ne soit pas (encore) implémenté la substitution du caractère barre oblique inverse \, c'est le caractère d'échappement. Pour être sûr, nous devrions voir le code, par exemple, de la version gnu de l'expansion de l'historique bash (mais il y avait la commande ci-dessus pour obtenir ce que vous essayez de faire ... alors je le prends paresseux ....).

Quelques notes:

  1. Nous sommes amenés à penser que cela fonctionnera avec chaque RegEx avec lequel nous travaillons sed, mais ce n'est pas garanti. La barre oblique inverse est le caractère d'échappement de l'extension et le problème est là. De plus le comportement de l'expansion est lié aux shoptoptions, il faut donc commencer à voir au cas par cas ...

  2. Lorsque vous collez la chaîne cd C:\Foo\Bardans votre shell bash, elle sera développée et apparaîtra pour l'interpréteur comme cd C:FooBar; sous cette forme, il sera également stocké dans la $_variable interne.
    Si vous avez plutôt collé cd "C:\Foo\Bar"ou cd 'C:\Foo\Bar'dans la $_ variable, vous devriez trouver C:\Foo\Bar.
    Étant donné que l'expansion de l'historique est effectuée immédiatement après la lecture d'une ligne complète, avant que le shell ne la décompose en mots, vous pouvez être tenté de commencer à l'utiliser avec un bashisme plus ou moins simple, par exemple, avec une dérivation de (peut-être en ajoutant :pou :q, "", l'analyse et ainsi de suite ...)

    !!:0 ${_//\\/\/}

    C'est le moment de se rappeler qu'il n'est pas sûr de commencer à jouer avec les chemins et les noms de fichiers , surtout s'ils proviennent du presse-papiers de Windows (lire en général la page Pourquoi ne pas analyser ls?, C'est essentiellement lié à la possibilité d'utiliser tab, espaces et sauts de ligne comme caractères corrects pour les noms de fichiers et de répertoires ...).
    De plus, lorsque vous collez un texte capturé avec la souris , vous pouvez également coller un espace de tête. Cela peut éviter que votre commande se termine dans l'historique (cela dépend des options du shell ...). Si c'est le cas, votre commande suivante !!sera une commande non contrôlée ... (voir un exemple dans une autre réponse ).Il s'agit d'un risque tangible inutile .

Conclusion

Les extensions d'historique introduisent des mots de la liste d'historique dans le flux d'entrée, ce qui facilite la répétition des commandes, l'insertion des arguments d'une commande précédente dans la ligne d'entrée actuelle ou la correction rapide des erreurs dans les commandes précédentes.

Si ce n'est pas facile, je commence à penser que nous faisons quelque chose de mal ;-)


Ad nauseam: une petite expérience

J'ai activé histverifydans le shell alors ...

shopt -s histverify
echo C:\Foo\Bar
!!:s|C|D| {1,2}A

puis j'appuie Enteret comme expansion vérifiée je trouve

echo D:\Foo\Bar {1,2}A

puis j'appuie à Enternouveau et ça fait écho

D:FooBar 1A 2A

Cela semble indiquer que le substitution failedest généré dans l'expansion de l'histoire traitée avant l' expansion de Brace , donc tout d'abord , et cela semble confirmer que le smodificateur de l' histoire n'a pas (encore) traité la substitution du \personnage comme une véritable expression régulière. ..

Hastur
la source
2

Vous pouvez utiliser sedpour remplacer et exécuter le résultat.

Il existe plusieurs variantes possibles pour une telle commande, mais sur mon bash seule celle-ci a fonctionné:

A=$(echo "!!\\" | sed 's,\\,/,g');eval $A

Le \\est ajouté à l' !!est au cas où la dernière commande se terminerait par une barre oblique inverse. Sans cela, le shell se confondra et demandera la poursuite de la ligne.

Vous pouvez également ajouter ceci en tant que fonction bash dans le fichier ~/.bashrc:

function slash {
   A=$(history | tail -n 2 | head -n 1 | cut -c 8- | sed 's,\\,/,g')
   eval $A
}

Voici comment cela fonctionne sur mon Bash sous Windows:

frapper

Pour expliquer comment la A=commande attribue la bonne valeur à la variable shell A:

  • tailprend les deux dernières lignes de l'historique des commandes, ce qui donne les lignes de cd \mnt\csuivi de slashlui-même. La partie de history | tail -n 2peut également s'écrire history 2.
  • head prenez la première ligne qui est cd \mnt\c
  • cut supprime le numéro de ligne fourni par history
  • sed remplace tous les anti-slash par des slashes
  • eval exécute le contenu de la variable A
harrymc
la source
Salut Harry. Désolé de dire que sur ma version 4.3.11 (1) de GNU bash, le A=$(echo "!!\\" | sed 's,\\,/,g');eval $Ane fonctionne pas lorsque la commande s'est terminée avec une barre oblique inverse. Vous êtes obligé d'ajouter un espace, par exemple à C:\Foo\Bar\ else, comme vous l'avez dit, le shell attendra la suite de la ligne. Vous pouvez également appuyer deux fois sur Entrée. Dans le premier cas, vous obtenez C:/Foo/Bar /(avec un espace avant le /; dans le second, cela générera une erreur. La fonction fonctionne bien.
Hastur
J'ai écrit la fonction parce que (1) il est beaucoup plus facile à utiliser et (2) le one-liner semblait trop dépendant de l'implémentation.
harrymc
-1

Je ne pense pas que vous puissiez faire ça. Vous devez copier / coller à nouveau.

Le problème n'est pas lié à la barre oblique étant également le séparateur de commande de substitution, car la commande de substitution accepte tout séparateur. Le problème concerne les barres obliques inversées à remplacer, qui ont déjà été traitées comme de simples échappées inutiles de caractère à leur droite.

Par conséquent, lorsque vous appelez la commande de substitution, la barre oblique inverse est déjà supprimée de la source. Essayez simplement de rappeler la commande telle quelle ( !!). Au lieu d'imprimer exactement la même commande, y compris c:\foo, il exécutera la commande moins les barres obliques inverses déjà traitées qui en résulteront c:foo.

Et évidemment, vous ne pouvez pas trouver ni remplacer un caractère manquant. Essayer de le faire déclenchera une erreur.

A. Loiseau
la source
1
Bien. C'est finalement faux puisque ce test fonctionne réellement: echo c:\Foosuivi de !!:gs,F,\\f,. Mais la substitution de la barre oblique inverse semble être une douleur.
A. Loiseau