Bash – wykrywanie wklejanego tekstu

Bracketed paste mode to dosyć mało znana funkcjonalność oferowana przez wiele emulatorów terminala. Przez lata korzystania z Linuksa, używania różnych powłok systemowych i terminali nie miałem o niej pojęcia i zapewne wciąż nie wiedziałbym o jej istnieniu, gdyby nie przypadek – bug w którymś z używanych przeze mnie programów, który ujawnił mi tę ciekawą opcję.

O co dokładnie chodzi? Kilkukrotnie spotkałem się z dziwnym zachowaniem xterma, polegającym na tym, że wklejany przeze mnie tekst był otaczany dodatkowymi znakami. Na przykład gdy chciałem wkleić znajdujący się w schowku systemowym tekst foo, na ekranie pojawiał się napis 0~foo1~. Kiedy sytuacja ta wystąpiła po raz kolejny, postanowiłem w końcu zainteresować się jej przyczyną. Okazało się, że winnym całego zamieszania jest właśnie tzw. bracketed paste mode, który uaktywniony przez którąś z aplikacji, prawdopodobnie nie został przez nią wyłączony (np. z powodu jej nieoczekiwanego zakończenia crashem) [1].

Świadom już istnienia tej funkcjonalności, zapragnąłem oczywiście dowiedzieć się o niej czegoś więcej i przetestować ją w praktyce. Genezą trybu „nawiasowego wklejania” jest sposób, w jaki aplikacje w systemach uniksopodobnych mogą otrzymywać dane. Źródłem danych wejściowych może być np. bezpośrednia interakcja z użytkownikiem, który wpisuje tekst w trakcie działania programu. Może to być też tekst przesłany za pośrednictwem potoków, odczytany z pliku, albo właśnie wklejony. Załóżmy, że mamy taki oto prosty skrypt, który używa komendy read:

Nadajemy mu uprawnienia wykonywalności (chmod +x input.sh) i sprawdzamy jak możemy dostarczyć do niego dane:

Rezultatem każdego z tych wykonań będzie wyświetlenie dostarczonego tekstu poprzedzonego fragmentem I received input. Oznacza to, że program traktuje wszystkie z powyższych danych wejściowych identycznie, niezależnie od źródła ich pochodzenia. Zazwyczaj jest to zaletą, ale czasami zdarzają się sytuacje, w których wiedza skryptu o tym, czy tekst został wklejony czy wpisany byłaby przydatna (rozróżnienie pozostałych dwóch sposobów też jest oczywiście możliwe – przykład). Powstaje zatem pytanie: w jaki sposób terminal może przekazać do programu tę informację, nie naruszając przedstawionej tu transparentności dotyczącej danych wejściowych? [2] Jednym z pomysłów, które można zastosować jest doklejanie tej informacji bezpośrednio do danych, najlepiej w taki sposób, aby był to ciąg znaków, którego raczej nikt przez pomyłkę nie wpisze w innej sytuacji.

W taki właśnie sposób działa bracketed paste mode. Wklejany tekst jest niczym nawiasami otaczany przez:

  • \e[200~ jako oznaczenie początku tekstu
  • \e[201~ jako oznaczenie jego końca

Pozostaje jeszcze ostatnia kwestia – jak aktywować/deaktywować ów tryb? Aby go włączyć należy wysłać na  standardowe wyjście \e[?2004h, zaś aby go wyłączyć trzeba wysłać \e[?2004l. Przykładowy skrypt, prezentujący możliwy sposób użycia tego trybu znajduje się poniżej:

Program ten zarówno włącza i wyłącza bracketed paste mode, jak i wykrywa wklejony tekst oraz wyłuskuje go spomiędzy oznaczeń jego początku i końca (tak, zwracam własnie uwagę na to, że $start*$end to nie żadne mnożenie, ale proste dopasowywanie do wzorca, a asterysk oznacza dowolny tekst znajdujący się pomiędzy wartościami ze zmiennych start oraz end). Użycie polecenia stty w liniach 14 oraz 16 odpowiada zaś za chwilowe wyłączenie trybu echo na czas pobierania od użytkownika tekstu – w skrócie mówiąc nic nie pojawia się na ekranie kiedy użytkownik wpisuje znaki, co jest przydatne np. gdy chcemy pobrać hasło lub jakąś inną poufną wartość.

Do czego to w ogóle wykorzystać? – zapytają niektórzy. Możliwości na pewno jest co najmniej kilka [3]. Jedna z nich, to częściowe zabezpieczenie użytkownika przez wklejeniem potencjalnie niebezpiecznego polecenia. Postępując według różnych tutoriali często zdarza nam się niemal bezmyślnie przeklejać z Internetu i wykonywać różne zlepki poleceń. Są to doskonałe warunki, aby paść ofiarą techniki WYSINWYC (What You See Is Not What You Copy) – przykład takiego zjawiska można znaleźć np. tutaj, przy czym autor słusznie zwraca uwagę na fakt, iż bracketed paste mode nie jest stuprocentowym zabezpieczeniem przed wszystkimi atakami tego typu.

Zupełnie inne zastosowanie omawianego trybu można znaleźć w programach takich jak edytory tekstu. Niektóre z nich, np. Vim, stosują automatyczne wcięcia również do wklejanego tekstu. Zazwyczaj nie jest to pożądane zachowanie, bo przecież tekst, który skądś kopiujemy ma już odpowiednie indentacje. Oczywiście w Vimie można ręcznie włączyć tryb wklejanie (:set paste), ale robienie tego za każdym razem staje się trochę uciążliwe. Rozwiązaniem problemu jest właśnie bracketed paste mode, który sprawia, że Vim wykryje kiedy wpisujemy tekst (i należy stosować do neigo wcięcia), a kiedy go wklejamy [4].

Na sam koniec ważna wskazówka dla osób, które nie miały jeszcze okazji zbyt dużo pracować w linuksowej konsoli i ze zdziwieniem odkrywają, że skróty Ctrl+C oraz Ctrl+V nie działają: skróty klawiszowe do kopiowania i wklejania w terminalu wymagają zazwyczaj użycia shiftaCtrl+Shift+C oraz Ctrl+Shift+V. Wszystkim innym chciałbym zaś zwrócić uwagę, że bracketed paste mode należy wyłączyć przed zakończeniem programu, ponieważ działa on w obrębie całej sesji terminala.

Źródła

  1. Copy-Paste in xfce4-terminal adds 0~ and 1~
  2. How do I disable the weird characters from “bracketed paste mode” on the Mac OS X default terminal?
  3. Bracketed paste mode, C. Irwin
  4. vim – Goodbye to :set paste