Het uitroepteken in de shell is niet om te schreeuwen. Wees lui.
Opmerking: Dit artikel is primair gericht op bash, csh, tcsh en zsh. Als dit niet jouw dagelijkse shell is, raad ik aan om de sectie over POSIX en fc te lezen; deze is waardevol voor elke POSIX-compatibele shell.
Schreeuw niet tegen je collega's; schreeuw in je shell
Jij en ik zijn waarschijnlijk heel verschillend; mijn keuze voor een editor doet jou waarschijnlijk huiveren, en jouw favoriete programmeertaal is statistisch gezien zeker niet de mijne. Er is echter één ding dat we allemaal gemeen hebben: we zijn een stel luie honden.
Command-line herhaling die we sociaal accepteren
$ ssh some-user@10.240.33.109
$ echo "done with some-user@10.240.33.109" >> ~/work/superfun-client/worklog
$ cd ~/work/superfun-client && cat TODO
$ ssh some-user@10.240.33.109 # <- vergeten iets te doen
Ik weet zeker dat we allemaal in een "pijltje-omhoog-rammel-hel" zijn beland bij het herhalen van commando's uit onze shell-geschiedenis. Maar wat als we onszelf die moeite konden besparen door direct naar vorige commando's en hun argumenten te verwijzen?
$ ssh some-user@10.240.33.109
$ echo "done with !:1" >> ~/work/superfun-client/worklog
$ cd !$:h && cat TODO
$ !ssh
Geen geschreeuw — alleen uitroeptekens.
Opmerking: De magie die in dit artikel wordt beschreven, is van toepassing op interactieve shells (tenzij anders geconfigureerd). Het is niet bedoeld voor gebruik in je nieuwe amazing.sh shell-script.
Herhaal jezelf niet (DRY)
Je hebt het vast al eens gehoord: "do not repeat yourself" (DRY), of dat nu was van die irritante collega met alle obscure trucjes, of dat je het zelf zei tijdens de laatste refactor-sessie die somehow langer duurde dan de federatievertraging op matrix.org.
$ apt install awesome-package # onvoldoende rechten
$ sudo !! # herhaal met sudo
$ mkdir /var/mnt/cache # de gebruikelijke dance om een map aan te maken
$ cd !$ # ga naar de map, geen vingerkrampen nodig
De meesten van ons zijn vanaf hun junior-tijd religieus geleerd om DRY overal toe te passen... behalve op de command-line?
$ touch ~/projects/work/awesome.sh
$ cd !$:h # ga naar de map waar awesome.sh in staat
$ ssh 10.240.33.109 -p2222 -i ~/.ssh/prod/ed25519
$ ssh 10.240.33.110 !:2* # zelfde flags, andere host
$ ffmpeg -i /recordings/a-sunny-day.mov !#:2:r.mkv
# resultaat: ffmpeg -i /recordings/a-sunny-day.mov /recordings/a-sunny-day.mkv
$ scp !$:r.* example.com:/media
# resultaat: scp /recordings/a-sunny-day.* example.com:/media
Wat is deze magie: de event designator?
Hoewel ze op het eerste gezicht intimiderend kunnen overkomen, volgen event designators altijd hetzelfde eenvoudige patroon:
![event][:word][:modifier]
![event]: Welke regel (bijv.!!,!-2,!ssh,!?needle?)[:word]: Welk deel van de regel (bijv.:0,:$,:*,:2-3)[:modifier]: Modificatie (bijv.:h,:t,:r,:e)
Event Designator
Het eerste deel direct na het uitroepteken geeft aan over welke regels we geïnteresseerd zijn. Hier zijn enkele voorbeelden:
!!: de vorige regel!-2: twee regels terug!1337: de 1337ste regel, zoals getoond doorhistory!ssh: de meest recente regel die begint met "ssh"!?dandelion?: de meest recente regel die "dandelion" bevat!#: de huidige regel, tot nu toe geschreven^ssh^scp^: vervang het eerste voorkomen vansshdoorscpin het vorige commando
Opmerking: Als de ! direct wordt gevolgd door een :, wordt er geen expliciete event gespecificeerd en verwijst de expressie naar de onmiddellijk voorgaande regel.
Word Designator
Na de event designator kun je specificeren welk deel van de regel je nodig hebt. In de onderstaande voorbeelden gaan we uit van het commando: /path/to/script.sh "hello world" --enable 1337
!:0: 1e woord →"/path/to/script.sh"!:1: 2e woord →"hello world"!:$: laatste woord →"1337"!:1-2: 2e tot en met 3e woord →"hello world" "--enable"!:*: alle argumenten →"hello world" "--enable" "1337"!:1-: alles behalve het laatste →"hello world" "--enable"!:2*: van het 3e woord tot het laatste →"--enable" "1337"
Opmerking: Er zijn verschillende korte versies die werken zonder dubbele punt, zoals !$ (equivalent aan !:$) en ! (equivalent aan !:).
Modifier
Modifiers maken het echt krachtig; ze stellen je in staat om specifieke delen van een argument te extraheren of te wijzigen.
!$:h: verwijder de bestandsnaam (dirname)!:1:t: verwijder het pad/voorvoegsel (basename)!:1:r: verwijder alleen de extensie!:1:e: behoud alleen de extensie!:s/hello/bye/: vervang de eerste 'hello' door 'bye'!:gs/foo/bar/: vervang alle 'foo' door 'bar'!ssh:p: print wat er uitgevoerd zou worden, zonder het daadwerkelijk uit te voeren
POSIX en de kracht van fc
Of jouw shell nu ! ondersteunt of niet, fc hoort in je gereedschapskist. Het kan gebruikt worden om het vorige commando te herhalen, maar de echte kracht ligt in het feit dat je je command-line kunt bewerken in de editor naar keuze, in plaats van direct in de shell.
Elke shell-avonturier zou fc minstens één keer per jaar moeten draaien:
$ fc # bewerk het vorige commando
$ fc -2 # bewerk het commando 2 stappen terug
$ fc grep # bewerk het laatste commando dat begint met 'grep'
$ fc -3 -1 # de laatste drie commando's, allemaal in één buffer
$ fc ssh -1 # van de laatste 'ssh'-regel tot aan de meest recente
$ fc -s ssh # voer direct het laatste commando uit dat begint met 'ssh'
$ fc -s hello=world ssh # vervang "hello" door "world" op de regel die begint met 'ssh'
fc gebruikt het programma dat is gespecificeerd in $FCEDIT. Als die niet bestaat, valt POSIX terug op ed, hoewel veel shells standaard $EDITOR gebruiken. Je kunt ook een editor specificeren met -e:
$ scp api.tar.gz prod-01:/tmp
$ ssh prod-01 systemctl restart api
$ curl -sf https://prod-01/health
$ fc -e 'sed -i s/prod-01/test-01/g' -3 -1
Bovenstaande voert de drie commando's opnieuw uit, waarbij prod-01 in elk commando wordt vervangen door test-01. Een geforceerd voorbeeld misschien, maar ontzettend handig als je in zo'n situatie zit.
Opmerking: fc maakt deel uit van de User Portability Utilities van POSIX. Zeer minimale shells zoals dash en busybox ash worden zonder deze functie geleverd.
Schreeuw verantwoord
Event designators, of fc in het geval van POSIX, zullen je leven niet plotseling veranderen in die van een über-10x-developer met een hoodie. Ze kunnen je echter overal tussen de vier en tweeënveertig toetsaanslagen per keer besparen, honderden keren per week, gedurende je hele carrière.
Hieronder staan de essentie; onthoud er een paar en je bent al mijlenver vooruit vergeleken met je vroegere zelf.
De regel van drie
!!: breidt uit naar het vorige commando.!needle: breidt uit naar het meest recente commando dat begint met "needle".!?needle?: matcht het meest recente commando dat "needle" bevat.
De krachtige vier
!$: het laatste argument van het vorige commando.!:0: handig wanneer je dat irritant gelegen script opnieuw moet draaien.!$:h: voor een eenvoudige extractie van het map-gedeelte.!$:t: voor wanneer je alleen de bestandsnaam wilt en niets anders.
Ik vertrouw mijn 'uitroepteken-fu' nog niet helemaal, enig advies?
Voor bash bestaat er shopt -s histverify, en in zsh is dat setopt HIST_VERIFY. In plaats van het commando direct blindelings uit te voeren (YOLO), zorgen deze instellingen ervoor dat de commando's ter plekke worden uitgebreid wanneer je op enter drukt. Hierdoor kun je het resultaat bewerken of weggooien als het niet is wat je bedoelde.
Je kunt ook de :p-modifier gebruiken om te printen wat er uitgevoerd zou worden zonder het echt te doen. De expansie komt in je geschiedenis terecht, zodat een simpele !! (of pijltje-omhoog + enter) het uitvoert zodra je er klaar voor bent.
En nu?
Ik denk dat er niets anders overblijft dan te oefenen. Refrein van het schreeuwen tegen je collega's, besteed de bespaarde toetsaanslagen aan praatjes bij het koffiezetapparaat, en onthoud voor altijd dat uitroeptekens niet bedoeld zijn om te schreeuwen — in ieder geval niet in je shell.
Veelgestelde vragen
Waarom niet ctrl-r gebruiken in plaats van een hoop dingen te onthouden?
ctrl-r (command-line history search) is extreem krachtig, maar het geeft je hooguit een sjabloon om aan te passen; event designators stellen je in staat om gedeeltelijke inhoud te extraheren. Bovendien: waarom zoeken als je al weet wat je wilt draaien? !ssh tegenover ctrl-r + ssh — de eerste is ook gewoon cooler™.
Waarom niet alt-. gebruiken in plaats van !$?
alt-. is geweldig voor waarvoor het ontworpen is: het itereren over de vorige "laatste argumenten" in je shell-geschiedenis. Maar het is ook beperkt tot precies dat ene ding.
Negeer je niet andere nuttige functies?
Ik streef niet naar een "of-of"-situatie in mijn artikelen. Gebruik wat het werk gedaan krijgt. En voor wat het waard is... doe nooit wat een vreemde online vertelt; persoonlijke voorkeuren en workflows zijn meer waard dan welk artikel dan ook (ongeacht de hoeveelheid obscure, soms vergeten magie).
! zit me alleen maar in de weg, hoe zet ik dit uit?
Ik wed dat velen zijn gegrepen door de "gebruiksvriendelijkheid" van event designators zonder te beseffen waar ze voor dienen:
$ echo "!dlrow olleh"
bash: !dlrow: event not found
Als je dit wilt uitschakelen zodat je ! overal kunt gebruiken, kun je set +H gebruiken in bash en setopt nobanghist in zsh. Voor andere shells raad ik aan de handleiding te raadplegen.
Je kunt ook gaan voor de volledige "magische collega"-stijl (gebruik wel deodorant in het openbaar) en een willekeurig teken naar keuze gebruiken:
$ histchars='%^#' # event-trigger, substitutie, commentaar
$ echo "hello world"
hello world
$ echo %$:s/world/the internet/
hello the internet
Vergeet niet je configuratie in het relevante .rc-bestand te zetten om de wijzigingen permanent te maken.
Verder lezen
Documentatie relevant voor event designators:
- Event Designators (gnu.org)
- Word Designators (gnu.org)
- Designator Modifiers (gnu.org)
Groetjes,