Wpisujesz komendę w konsoli, uruchamiasz… i coś jest nie tak. Trzeba poprzednie polecenie zmodyfikować i odpalić ponownie. Co teraz? Wpisywanie wszystkiego od zera nie wchodzi w grę. Można pomóc sobie myszą, zaznaczać, kopiować, wklejać, dusić backspace – są jednak szybsze opcje, niewymagające odrywania rąk od klawiatury. Opowiem o nich, zaczynając od tych wspominanych w większości poradników (jak !!
i !$
), kończąc na rzeczach mniej znanych: odwoływaniu się do argumentów po indeksie i usuwaniu z nich rozszerzeń i fragmentów ścieżki. Opisywane mechanizmy działają w Zsh i Bashu. Część może uprościć pisanie skryptów.
Spis treści
Absolutne podstawy
Załóżmy, że wykonaliśmy jakieś polecenie i chcemy coś poprawić:
% cp result /tmp cp: -r not specified; omitting directory 'result'
Tu przeoczyliśmy, że result
to katalog i polecenie się nie powiodło. Trzeba spróbować raz jeszcze, tym razem dodając potrzebny przełącznik -r, czyli: cp -r result /tmp
. Jak?
- na klawiaturze: ↑ – przywołuje ostatnie polecenie
- na klawiaturze: Ctrl+← – przesuwa kursor o słowo w lewo
- to bashowy skrót, w Zsh domyślnie jest inaczej: Alt+b słowo w lewo (backward), Alt+f słowo w prawo (forward); ja przemapowałem sobie na strzałki, żeby nawigacja działała tak, jak w IntelliJ czy LibreOffice Write
- u biednych użytkowników Maca nie działa ani pierwszy, ani drugi sposób, ani systemowy skrót na przechodzenie po słowach, a na StackOverflow jest dużo przeczących sobie odpowiedzi – ja umywam ręce
Wciskamy raz klawisz w górę, potem 2 razy drugi skrót, mamy kursor na początku result
, piszemy -r, enter i gotowe.
Inna sytuacja: pomyłka w pierwszym argumencie (miało być result
, wpisaliśmy results
).
% cp results ../../src/test/resources/com/example/expected cp: cannot stat 'results': No such file or directory
Nie trzeba ganiać kursorem. Można wpisać:
% cp result !$
co zostanie przetłumaczone na:
cp result ../../src/test/resources/com/example/expected
Albo: polecenie jest dobre, ale zabrakło sudo
na początku:
% apt install make gcc E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied) E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?
Można wpisać:
sudo !!
a będzie wykonane:
sudo apt install make gcc
Jeśli apt
nie był ostatnim poleceniem, można go przywołać w ten sposób:
sudo !apt
Ja bardzo często używam !$
przy tworzeniu nowych projektów:
cd ~/devel/bugs mkdir spring-data-id-accessor cd !$ gradle init
Podsumowując:
- !$ jest zamieniane na ostatni argument poprzedniego polecenia
- !! jest zamieniane na całe ostatnie polecenie
- !apt jest zamieniane na ostatnie polecenie z historii, które zaczyna się od „apt”
Analogicznie:
% echo raz dwa trzy raz dwa trzy % echo !^ raz
% echo raz dwa trzy raz dwa trzy % rm !* rm: cannot remove 'raz': No such file or directory rm: cannot remove 'dwa': No such file or directory rm: cannot remove 'trzy': No such file or directory
- !^ to pierwszy argument poprzedniego polecenia (jak w wyrażeniach regularnych: ^ to początek a $ to koniec)
- !* to wszystkie argumenty (analogicznie do $* w skryptach)
Zaawansowany dostęp do argumentów
Tym razem skupmy się na jednym przykładzie: mamy plik i chcemy go rozpakować.
unzip -q ci-job-4321-tests.zip -d /tmp/ci-job-4321-tests
Jak zrobić, żeby się nie orobić? Zaprezentuję kilka podejść.
Ręczne wpisywanie i tabulator
Możemy tak:
unzip -q ci-<tab> -d /tmp/ci<enter>
Problem: rozpakowujemy do /tmp/ci/
niezależnie od tego, jak nazywał się oryginalny plik. Tak się nie da, gdy mamy dużo zipów i wszystkie rozpakowujemy – przemieszają się, a chcemy, żeby każdy dostał się w inne miejsce.
Kopiowanie całości myszą
Klikając dwukrotnie gdziekolwiek wewnątrz „wyrazu”, zaznaczamy go. Wtedy wciśnięcie środkowego klawisza myszy wklei go na pozycję kursora.
Wada: wkleiliśmy z rozszerzeniem. Albo trzeba jeszcze skasować rozszerzenie, albo zgadzamy się na katalog z rozszerzeniem .zip
. Plus trzeba oderwać ręce od klawiatury i machać myszką.
Kopiowanie części myszą
Jak wyżej, tylko zamiast zaznaczać dwukrotnym kliknięciem, normalnie ciągniemy zaznaczenie. W ten sposób możemy zaznaczyć bez rozszerzenia. W praktyce sposób zawodny: łatwo spiesząc się zaznaczyć o 1 znak za dużo lub za mało.
Kopiowanie całości bez myszy
To samo, ale bez myszy:
- Ctrl+w kasuje poprzednie słowo
- Ctrl+y wkleja („yank”)
- Alt+Backspace kasuje do slasha lub kropki (w Bashu)
U mnie w Zsh podpiąłem sobie pod Alt+Backspace zachowanie podobne jak w Bashu, ale inaczej zdefiniowałem granicę: jako slash i znak równości. W ten sposób jeśli mam opcję --configuration=compileClasspath
to skrót wytnie mi wartość opcji, a nie całą opcję. Kod inspirowany rozwiązaniem ze StrackOverflow:
backward-kill-dir () { local WORDCHARS=${WORDCHARS/=\/} zle backward-kill-word } zle -N backward-kill-dir bindkey '^[^?' backward-kill-dir
Kopiowanie części bez myszy
W Zsh działają jeszcze takie skróty:
- Ctrl+spacja zaczyna zaznaczanie tekstu
- wtedy ← i → rozszerzają zaznaczenie
- Esc+w kopiuje zaznaczony tekst
- Ctrl+y wkleja zaznaczenie (jak poprzednio)
Nieintuicyjna rzecz: zaznaczenie trzeba „przeciągnąć” o 1 znak za daleko, aż do kropki – kropka nie zostanie skopiowana.
W praktyce jest to tak niewygodne i wolne, że jak dla mnie zostaje tylko ciekawostką.
Odwołanie się do argumentu
unzip -q ci-<tab> -d /tmp/!#:2<enter>
zostanie rozwinięte do:
unzip -q ci-job-4321-tests.zip -d /tmp/ci-job-4321-tests.zip
- !# to aktualna linia poleceń
- !#:2 to drugi argument z aktualnej linii poleceń
Jest lepiej, bo nie trzeba odrywać rąk od klawiatury. Gorzej, że znowu kopiujemy niechciane rozszerzenie.
Manipulacja argumentem
unzip -q ci-<tab> -d /tmp/!#:2:r<enter>
zostanie rozwinięte do:
unzip -q ci-job-4321-tests.zip -d /tmp/ci-job-4321-tests
- :r usuwa rozszerzenie zostawiając tylko root name
Więcej o dostępie do argumentów
Już widzę wątpliwości: po co mam pisać, skoro świetnie władam myszką. Moja odpowiedź:
- nazwy niewygodne do zaznaczania: za długie do ciągnięcia po nich myszką, wypadające na łamaniu linii, ze specjalnymi znakami, które mylą ludzkie oko albo psują zaznaczanie podwójnym kliknięciem
- podwójne klikanie w konsoli na
Pl-Warszawa.oga
zaznacza całą nazwę pliku, ale przyZh-华沙.oga
zaznacza tylko 1 znak
- podwójne klikanie w konsoli na
- skrypty
Modyfikator :r
działa na każdą zmienną, nie tylko na argumenty. Może być bardzo przydatny w skryptach:
for i (*un*.jpg); do convert $i -resize 50% ${i:r}-small.jpg; done
${i:r}
wypisuje zmienną i
z wyciętym rozszerzeniem. W ten sposób z pliku sun.jpg
dostaniemy zmniejszoną wersję w sun-small.jpg
.
Jeszcze jeden przykład.
mv ../../01/in.txt !#^:h/out.txt
daje
mv ../../01/in.txt ../../01/out.txt
- !#^ to pierwszy argument z aktualnej linii (to samo, co !#:^, ale przy ^ nie trzeba dwukropka)
- :h usuwa 1 poziom katalogu („head”)
Ściągawka
Ogólna składnia to:
event:word:modifier
- event to oznaczenie, co wybrać z historii poleceń
!!
poprzednie polecenie!#
aktualna linia!-2
2 polecenia temu
- word to które słowa wziąć z wybranej wcześniej linii
^
pierwszy argument, nie trzeba poprzedzić dwukropkiem$
ostatni argument, nie trzeba poprzedzić dwukropkiem0
pierwsze słowo, czyli sama komenda8
ósmy argument, czyli dziewiąte słowo
- modifier to operacja na wybranym wcześniej słowie
r
usuwa rozszerzenie (root name)h
usuwa poziom katalogu (head)
Części :word
i :modifier
są opcjonalne.
Powtarzanie z modyfikacją
rm -r src/main/java/com/example/compat ^main^test^
daje w wyniku drugiego polecenia
rm -r src/test/java/com/example/compat
Zastępowane jest tylko pierwsze wystąpienie. Żeby zastąpić wszystkie wystąpienia, trzeba dodać modyfikator :G
: ^main^test^:G
.
Dalsza lektura
To nie wszystkie możliwości, ale myślę, że zapamiętanie powyższych to i tak spory wysiłek. Dla ciekawych polecam pełną dokumentację Zsh o Expansion. Nie jest to najłatwiejszy w czytaniu tekst, ale z przykładami z tego artykułu powinno iść łatwiej. Nie trzeba uczyć się wszystkiego na pamięć – można sięgnąć do dokumentacji przy pisaniu skryptów.
Jeśli pominąłem jakąś z podstawowych sztuczek – zapraszam do komentowania.