На этой лекции будут затронуты некоторые аспекты работы с терминалом: история его появления, особенности работы с терминалом. Также будут рассмотрены языки склейки (в частности, Shell и zsh), позвояющие настроить некоторый универсальный способ взаимодействия с утилитами.
Терминал - это устройство, которое обеспечивает ввод-вывод байтов на компьютер. Ровным счётом, всё. Никаких волшебных свойств, накрученных механизмов. Просто что-то, куда можно вводить байты и откуда их можно получать, плюс минимальная подкапотная обработка для перевода с символьного языка на машинные команды.

Работать с компьютером вслепую было очень неудобно. Уже существовали перфокарты, которые можно было сделать заранее под каждое действие компьютера, но человеческий фактор никто не отменял, и хотелось бы при возникновении какой-то ошибки не перечитывать выколотые точки на бумажке и не искать, где какая информационная лампочка у компьютера загорелась, а вот в одно место куда-то получать ответ. А если с этого места можно будет эти самые команды компьютеру передавать вместо кучи перфокарт, так ещё и делать это прямо во время работы, жить стало бы сильно проще.
Первым терминалом стал Телетайп. Он представлял собой пишущую машинку, подключённую к компьютеру, которая печатает на бумажку текст, после чего этот текст можно было автоматически загнать в компьютер, он обрабатывался, как последовательность байт, отрабатывал команду, после чего на бумажку выводил ответ.
Это нововведение в те годы произвело невероятный скачок в работе с ЭВМ. Ничего роднее пишущей машинки для человека того времени не было. Более того, было понятно, как и эту вещь можно дальше улучшать и модифицировать. С приходом в общее пользование электронно-лучевых трубок бумажная лента заменилась на экран, но идеи, которые вложили тогда для работы через терминал, живут и актуальны до сих пор. Просто потому что лучше придумывать и не надо, всё уже максимально адаптировано.
Телетайпы стали настолько неотъемлемой частью компьютера, что современные технологии кишат отсылками на это явление:
/dev/tty как сокращение от /devices/teletypestelnet — сетевой протокол для реализации текстового терминального интерфейса по сети - также имеет своё название именно благодаря телетайпам (teletype network)Также ещё с тех времён остались классические текстовые построчные редакторы. «На листочке» работу с полным файлом трудно делать. А построчно смотреть, изменять и т.д. - вполне))
Рассмотрим небольшой примерчик с использованием одного из таких редакторов — ed:
user@localhost:~> cat text
First str
Second str
Third str
user@localhost:~> ed -p 'ed> ' text # 'ed> ' — подсказка в диалоге редактора
31 # Размер файла
ed> 3 # Перейдём к третьей строке
Third str
ed> i # Вставим текст перед ней
Hehehe
. # Единственная точка в строке — конец ввода
ed> ,p # Выведем весь текст
First str # полный формат: <начало>,<конец>p
Second str # по умолчанию — начало и конец файла
Hehehe
Third str
ed> 1 # Перейдём к первой строке
First str
ed> s/First/Hahaha/ # Заменим First на Hahaha
ed> . # Выведем текущую строку
Hahaha str
ed> ,p # Выведем весь текст
Hahaha str
Second str
Hehehe
Third str
ed> w # Запишем обратно в файл…
39
ed> q # …и выйдем
user@localhost:~> cat text
Hahaha str
Second str
Hehehe
Third str
user@localhost:~>
Итак, с историей всё понятно. Основная мысль: «Терминал - основа управления компьютером». Но отсюда идёт логичный вопрос: а как управлять? Просто писать команды? А если я хочу просто текст в виде команды написать, а не команду? Нужно как-то отделять, когда мы вводим просто символы, а когда управляющие команды.
Именно с такими размышлениями разработчики той самой логики работы терминала выделили некоторые байты в качестве управляющих:
raw mode)\n и возврата каретки \r. В UNIX-системах, а вслед за ними и в Linux, это поведение унифицировали: в конце строки стоит один символ (это \n), а при вводе-выводе в cooked mode он преобразуется, если это требуется, в два в нужном порядке. Дело осложняется тем, что в других операционых системах (Windows и ранних версиях MacOS) эту унификацию пытались делать по-другому, отчего в raw mode виде файлов происходила символьная неразбериха.Ctrl+C посылается символ с ASCII-кодом 3. На терминалах клавиша Ctrl просто обнуляла шестой бит передаваемого символа: код C — 01000011₂, код Ctrl+C — 00000011₂. Этот символ не передаётся в программу, которая считывает данные с терминала; вместо этого операцонная система посылает этой программе сигнал SIGINT.Ещё одно наследие телетайпов, которое дожило до наших дней, — это подчёркивание и двойная печать. Для редактирования текста в пишущей машинке использовались возможности сдвига каретки; так, например, для подчёркивания символа каретку отгоняли назад и печатали подчёркивание поверх (получается, «под низ»?) символа; для создания жирного шрифта делали множественную повторную печать. В подсистеме просмотра руководств Linux — man — текст руководства размечается именно таким способом:
nroff:
```console
user@localhost:~> xz -d < /usr/share/man/man1/man.1.xz | nroff -mandoc -Tutf8
MAN(1) Manual pager utils MAN(1)NAME man - an interface to the system reference manuals
SYNOPSIS man [man options] [[section] page …] … man -k [apropos options] regexp … …
* Никаких следов разметки тут не видно, но если прибавить к выводу шестнадцатеричный дамп (байтовый вывод), мы увидим конструкции типа `_` + `backspace` + `символ` и `символ`+ `backspace` + `символ`:
```shell
user@localhost:~> xz -d < /usr/share/man/man1/man.1.xz | nroff -mandoc -Tutf8 | hexdump -C | head -16
00000000 4d 41 4e 28 31 29 20 20 20 20 20 20 20 20 20 20 |MAN(1) |
00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 4d 61 | Ma|
00000020 6e 75 61 6c 20 70 61 67 65 72 20 75 74 69 6c 73 |nual pager utils|
00000030 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000040 20 20 20 20 20 20 20 20 4d 41 4e 28 31 29 0a 0a | MAN(1)..|
00000050 0a 0a 4e 08 4e 41 08 41 4d 08 4d 45 08 45 0a 20 |..N.NA.AM.ME.E. |
00000060 20 20 20 20 20 20 6d 61 6e 20 2d 20 61 6e 20 69 | man - an i|
00000070 6e 74 65 72 66 61 63 65 20 74 6f 20 74 68 65 20 |nterface to the |
00000080 73 79 73 74 65 6d 20 72 65 66 65 72 65 6e 63 65 |system reference|
00000090 20 6d 61 6e 75 61 6c 73 0a 0a 53 08 53 59 08 59 | manuals..S.SY.Y|
000000a0 4e 08 4e 4f 08 4f 50 08 50 53 08 53 49 08 49 53 |N.NO.OP.PS.SI.IS|
000000b0 08 53 0a 20 20 20 20 20 20 20 6d 08 6d 61 08 61 |.S. m.ma.a|
000000c0 6e 08 6e 20 5b 5f 08 6d 5f 08 61 5f 08 6e 20 5f |n.n [_.m_.a_.n _|
000000d0 08 6f 5f 08 70 5f 08 74 5f 08 69 5f 08 6f 5f 08 |.o_.p_.t_.i_.o_.|
000000e0 6e 5f 08 73 5d 20 5b 5b 5f 08 73 5f 08 65 5f 08 |n_.s] [[_.s_.e_.|
000000f0 63 5f 08 74 5f 08 69 5f 08 6f 5f 08 6e 5d 20 5f |c_.t_.i_.o_.n] _|
user@localhost:~> xz -d < /usr/share/man/man1/man.1.xz | nroff -mandoc -Tutf8 | less
…
Вопрос: А как? Нельзя же вывести «подчёркнутый байт»?
Ответ всё тот же: часть байтов, выводимых на терминал, воспринимаются не как символы для отображения, а как управляющие. Типичные пример — всё те же перевод строки (который опускает курсор — позицию «каретки телетайпа» — на строку ниже) и возврат каретки (который возвращает курсор в начало той же строки). Правда, терминал в обрабатываемом режиме заменяет перевод строки на оба этих символа, так что включим в примере режим raw:
user@localhost:~> stty raw; echo -e 'qwe\nrty\nblahblah\rQQ!\nQKRQ'; stty cooked
qwe
rty
QQ! blahblah
QKRQ
user@localhost:~>
Дальше — больше. Появились целые последовательности символов, которые не отображались на экране терминалов, а изменяли поведение самих терминалов. Чаще всего (но не всегда!) такие последовательности начинались с символа ESCAPE, вследствие чего получили название ESС-последовательностей (далее по тексту для краткости — EscSeq). Один из примеров стандарта на EscSeq — Управляющие последовательности ANSI, но таких стандартов, иногда ничем друг на друга не похожих, насчитывалось несколько сотен.
Поэтому в UNIX и Linux-системах стали использовать специальные библиотеки, при обращении к которым пользователь описывает свойства текста, а они превращают их в последовательность управляющих символов. Вот так, например, можно выводить полужирный текст в сценариях на shell:
user@localhost:~> bold=$(tput bold)
user@localhost:~> normal=$(tput sgr0)
user@localhost:~> echo "Really ${bold}bold${normal} text" > text
user@localhost:~> cat text
\(\textrm{Really\ \textbf{bold}\ text}\)
user@localhost:~> hexdump -C text
00000000 52 65 61 6c 6c 79 20 1b 5b 31 6d 62 6f 6c 64 1b |Really .[1mbold.|
00000010 28 42 1b 5b 6d 20 74 65 78 74 0a |(B.[m text.|
0000001b
EscSeq решили сразу две проблемы: как управлять внешним видом дисплея текстового терминала, передавая на него только байты, и как получать с клавиатуры текстового терминала информацию о том, что нажата не-буквенная клавиша (например, End или F10) — разумеется, такая клавиша посылает на ввод EscSeq.
С помощью EscSeq реализованы все возможные операции с терминалом: печать в любом месте экрана, работа с атрибутами текста, очистка частей экрана. В частности, на EscSeq построена поддержка цветных текстов в терминале.

Почти всегда мы работаем с какой-то машиной, или фреймворком, или сетевой средой, заполненной командами и утилитами. Всё это окружение уже умеет решать наши задачи по обработке каких-то входных данных, преобразованию и передаче их. Но при разработке отдельных утилит разработчик далеко не всегда закладывает ориентир на работу со сторонними утилитами и, тем более, пользовательскими программами. Или взаимодействие со сторонним приложением может требовать довольно сложных синтаксических конструкций. Языки склейки, применяющиеся в качестве языков работы основного интерфейса управления нашей системы (в нашем случае, это командная строка), отвечают трём основным требованиям нашей работы:
Unix Shell — это удобный интерпретатор командной строки, поддерживающий работу с переменными, условные операторы, циклы и другие базовые конструкции языков программирования, а, главное, умеющий просто и быстро работать с установленными в систему программами, а также объединять их. Shell преимущественно работает с текстовыми данными, вследствие чего в системах, поддерживающий его, очень много утилит для работы с текстом
В нашем конспекте мы будем обращаться к «классическому» Shell-у, а также к интерпретатору Z Shell
Для ускорения работы интерпретатора в нём организован однопроходный синтаксический анализ скрипта, из-за чего для однозначного определения переменных среди текстового потока они выделяются спецсимволом $. При этом для задания переменных этого не нужно, определение произойдёт за счёт однозначной интерпретации операции присваивания
user@localhost:~> zsh
localhost% a=2
localhost% echo a
a
localhost% echo $a
2
localhost% a=4
localhost% echo $a
4
localhost%
Арифметика в Shell устроена через внутреннее $-представление результата выражения или через встроенную утилиту expr
localhost% A=2; B=3
localhost% echo $A
2
localhost% echo $B
3
localhost% echo $((A+B))
5
localhost% echo $((A+B-13))
-8
localhost% expr $A + $B
5
Так как интерпретатор может взаимодействовать с программами и запускать их на исполнение, при этом сам являясь программой, для пользователей открывается возможность создания вложенных пространств имён, в которых мы можем обрабатывать Shell-скрипты. Для взаимодействия пространств имён мы можем экспортировать переменные из внешнего пространства во внутреннее (обратное невозможно)
user@localhost:~> zsh
localhost% A=1
localhost% echo $A
1
localhost% zsh
localhost% echo $A
localhost% ASD=123
localhost% echo $ASD
123
localhost% exit
localhost% echo $ASD
localhost% echo A
A
localhost% export A
localhost% zsh
localhost% echo $A
1
localhost% exit
localhost% exit
user@localhost:~>
Shell поддерживает связывание имён на целые скрипты.
user@localhost:~> A() { echo qq; }
user@localhost:~> A
qq
user@localhost:~>
При работе с данными Shell может перед непосредственным выводом проводить пост-обработку полученных результатов
user@localhost:~> A=123.456.678
user@localhost:~> echo ${A}b # вывод форматированного текста
123.456.678b
user@localhost:~> echo $Ab # считает, что это переменная из двух букв и, очевидно, не находит её в своём словаре
localhost% A=123/456/789
localhost% echo $A
123/456/789
localhost% echo ${A}
123/456/789
localhost% echo ${A%/*} # Отрезание хвоста данных (отрезает последнее совпадение шаблону /*)
123/456
localhost% echo ${A%%/*} # Жадное отрезание хвоста (по первому совпадению)
123
localhost% echo ${A#*/} # отрезание головы (по первому совпадению)
456/789
localhost% echo ${A##*/} # Жадное отрезание головы (по последнему)
789
localhost% A=123.426.678
localhost% echo ${A/2/E} # замена первого совпадения
1E3.426.678
localhost% echo ${A//2/E} # Замена всех совпадений
1E3.4E6.678
Мы можем запускать сразу последовательности команд Shell — сценарии, или скрипты. При этом нам доступна возможность запуска их в фоновом режиме для продолжения
localhost% A=Hello
localhost% echo $A
Hello
localhost% {sleep 1; echo 1; sleep 1; echo 2; sleep 1; echo QQ} # запуск скрипта в данном* процессе
1
2
QQ
localhost% {sleep 1; echo 1; sleep 1; echo 2; sleep 1; echo QQ} & # запуск скрипта фоновым процессом — можем продолжать работу
[1] 189666
localhost% echo $A # успеваем вызывать другие команды Shell
Hello
1
localhost% echo $A
Hello
2
localhost% echo $A
Hello
QQ
[1] + 189666 done { sleep 1; echo 1; sleep 1; echo 2; sleep 1; echo QQ; }
localhost%
Для удобства передачи данных на входной и с выходного потоков программ в Shell существуют спецсимволы перенаправления:
< + перенаправление ввода> + перенаправление вывода с перезаписыванием итогового места>> + перенаправление вывода с дозаписыванием итогового места2> / 2>> + перенаправление потока ошибокlocalhost% ls
bin Desktop dwe Music o3 Py.py
d Documents file o oqwer pythonprac
d1 dotfiles LinuxAppDev o1 Pictures Templates
d34 Downloads Methodics_of_LinuxAppDev o2 Public Videos
localhost% ls > o
localhost% cat o
bin
d
d1
d34
Desktop
Documents
dotfiles
Downloads
dwe
file
LinuxAppDev
Methodics_of_LinuxAppDev
Music
o
o1
o2
o3
oqwer
Pictures
Public
Py.py
pythonprac
Templates
Videos
localhost% wc
qwert
asdfghj
zxcvbnm,lkjhgfdsa
3 3 32
localhost% wc < o
24 24 169
| — конвейер, вывод левого операнда передаётся на вход правому операндуuser@localhost:~> ls -l /usr/bin | grep ls | wc
49 453 2964
user@localhost:~>
localhost% { ls o?; ls d?;}
o1 o2 o3
d1
localhost% { ls o*; ls d*;}
o o1 o2 o3 oqwer
d d1 d34 dwe
dotfiles:
alacritty bash init_bash_vim_conf.bash lf neofetch nixos nvim vim
localhost% { ls o?; ls q?;} > o 2>&1
localhost% cat o
o1
o2
o3
zsh: no matches found: q?
<< — Ввод до определённых данных с входного потокаuser@localhost:~> cat > file <<EOF
> Hello
> Im
> Here
>
> EOF
user@localhost:~> cat file
Hello
Im
Here
user@localhost:~>
\(\textrm{if\ \textbf{команды};\ then\ \textbf{команды-True};\ else\ \textbf{команды-False};\ fi}\)
В качестве условия у нас используется статус завершения последней из команд
user@localhost:~> if ls o?; then echo QQ; fi
o1 o2 o3
QQ
user@localhost:~> if ls q?; then echo QQ; fi
ls: cannot access 'q?': No such file or directory
user@localhost:~> if ls q?; then echo QQ; else echo "==$?"; fi
ls: cannot access 'q?': No such file or directory
==2
Цикл с условием:
\[\textrm{while\ \textbf{команды};\ do\ \textbf{команды};\ done}\]Цикл по последовательности:
\[\textrm{for\ \textbf{переменная}\ in\ \textbf{слова};\ do\ \textbf{команды};\ done}\]localhost% while [ $a -lt 6 ]; do
echo "Hello $a"; read a; done
Hello 1
3
Hello 3
2
Hello 2
5
Hello 5
8
localhost%
localhost% for i in o? d?; do echo "Find ${i}"; done
Find o1
Find o2
Find o3
Find d1
localhost%
Для проверки условия в примерах выше мы использовали утилиту test, а, точнее, её шорт-кат [ (он принимает 4 аргумента: левый операнд, операцию, правый операнд и закрывающую скобку ] )
user@localhost:~> if test 1 = 2; then echo qq; else echo qwer; fi
qwer
user@localhost:~> if test 2 = 2; then echo qq; else echo qwer; fi
qq
user@localhost:~> if [ 2 = 2 ]; then echo qq; else echo qwer; fi
qq
Также нами использовалась утилита read, работающая построчным считывателем данных, разбивающим их разделителями на элементы для связывания с переменными. При этом последнее связывание происходит на весь оставшийся незаписанный поток информации
user@localhost:~> cal | while read a b c; do
> echo $c $b $a; done
2024 October
Tu We Th Fr Sa Mo Su
3 4 5 2 1
8 9 10 11 12 7 6
15 16 17 18 19 14 13
22 23 24 25 26 21 20
29 30 31 28 27
user@localhost:~> cal
October 2024
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
user@localhost:~> cal | while read a b c; do echo $c // $b // $a; done
// 2024 // October
Tu We Th Fr Sa // Mo // Su
3 4 5 // 2 // 1
8 9 10 11 12 // 7 // 6
15 16 17 18 19 // 14 // 13
22 23 24 25 26 // 21 // 20
29 30 31 // 28 // 27
// //
user@localhost:~>
Вид выражения:
case <выражение> in
<шаблон 1>) <команды> ;;
...
<шаблон N>) <команды> ;;
*) <команды> ;;
esac
Шаблоны выражений могут содержать спецсимволы; быть множеством паттернов, разделённых символом |
languages.sh
#!/bin/bash
echo -n "Enter the name of a country: "
read COUNTRY
echo -n "The official language of $COUNTRY is "
case $COUNTRY in
Lithuania)
echo -e "Lithuanian\n"
;;
Romania | Moldova)
echo -e "Romanian\n"
;;
Italy | "San Marino" | Switzerland | "Vatican City")
echo -e "Italian\n"
;;
*)
echo -e "unknown\n"
;;
esac
localhost% ./languages.sh
Enter the name of a country: Romania
The official language of Romania is Romanian
localhost% ./languages.sh
Enter the name of a country: Russia
The official language of Russia is unknown
localhost%
Работа с Shell, как мы говорили, непосредственно связана с системными утилитами. Большая их часть — это внешние программы, работающие с потоком данных. Кроме них в системе есть внутренние программы, которые умеют подменять окружение и редактировать его.
Разберём несколько полезных утилит, предустановленных в систему (основная документация по доступным утилитам лежит здесь).
Говоря о работе с файлами, нельзя не упомянуть китов этой области — утилиты cp для копирования файлов, mv для переноса файлов, ls для просмотра содержимого директории, cat для просмотра содержимого файла. Поскольку их работа более-менее понятна всем, рассмотрим специализированные программы для работы с файлами и файловой системой.
find находит файлы по предикатам в указанном дереве файловой системы. Среди предикатов даже есть исполняющий, передающий найденные данные в заданный скриптuser@localhost:~> find /usr/bin -perm -2000
/usr/bin/chage
user@localhost:~>
user@localhost:~> find /usr/bin -perm -2000 -exec ls -l {} \;
-rwxr-sr-x 1 root shadow 84944 Jul 8 14:13 /usr/bin/chage
user@localhost:~>
Работа с текстом — одна из важнейших в рамках управления системой, где почти все программы принимают и возвращают текстовые данные. При этом очень важно учитывать возможность работы с локализованными данными, так как от того, под какую локаль ориентировалась разработка утилиты, результаты работы могут сильно отличаться.
localhost% ls /etc/ | sort | tail -20 | tee text1
uefi
updatedb.conf
UPower
usb_modeswitch.conf
vconsole.conf
vnc
wgetrc
wofi
wpa_supplicant
X11
xattr.conf
xdg
xfce_defaults.conf
YaST2
zprofile
zsh_command_not_found
zsh_completion.d
zshenv
zshrc
zypp
localhost% ls /etc/ | LC_ALL=C sort | tail -20 | tee text2
tuned
udev
udisks2
uefi
updatedb.conf
usb_modeswitch.conf
vconsole.conf
vnc
wgetrc
wofi
wpa_supplicant
xattr.conf
xdg
xfce_defaults.conf
zprofile
zsh_command_not_found
zsh_completion.d
zshenv
zshrc
zypp
localhost% diff -u text1 text2
--+ text1 2024-10-04 12:43:06.524414464 +0300
+++ text2 2024-10-04 12:43:17.284449326 +0300
@@ -1,17 +1,17 @@
+tuned
+udev
+udisks2
uefi
updatedb.conf
-UPower
usb_modeswitch.conf
vconsole.conf
vnc
wgetrc
wofi
wpa_supplicant
-X11
xattr.conf
xdg
xfce_defaults.conf
-YaST2
zprofile
zsh_command_not_found
zsh_completion.d
localhost%
cut возвращает срез со входного потока (файла или потока ввода) по заданным параметрамlocalhost% cat > file
Hello my dear friend
localhost% cut -c 7-15 file
my dear f
localhost% cut -d ' ' -f 2 file
my
localhost%
head и tail выводят первые и последние соответственно N элементов входного потокаlocalhost% ls /etc | head -n 10
adjtime
alacritty
aliases
aliases.d
aliases.lmdb
alsa
alternatives
apparmor
apparmor.d
audit
localhost% ls /etc | tail -n 10
xattr.conf
xdg
xfce_defaults.conf
YaST2
zprofile
zsh_command_not_found
zsh_completion.d
zshenv
zshrc
zypp
localhost%
Комбайны — важный класс утилит, взаимодействующий со входным потоком на основе регулярных выражений.
grep — утилита поиска шаблонов во входном потоке. Своё название утилита получила от своего прародителя — редактора ed, где выступала одной из управляющих функций. В редакторе команда имела формат g/re/<action>, которая расшифровывалась, как “Global search / Regular Expression / <>” — Найти совпадение шаблону, заданному регулярным выражением, и произвести действие над элементом с этим шаблоном. g/re/p — частный случай для вывода найденных элементовsed — потоковый текстовый редактор.
s/регулярное выражение/подстановка/модификаторы — замена одного шаблона на другой в указанном местеlocalhost% ls -l > file
localhost% cat file | grep D
drwxr-xr-x 1 stephen stephen 0 Dec 8 2023 bin
drwxr-xr-x 1 stephen stephen 152 Sep 27 21:23 Desktop
drwxr-xr-x 1 stephen stephen 78 Sep 15 22:35 Documents
drwxr-xr-x 1 stephen stephen 896 Sep 28 21:21 Downloads
drwxr-xr-x 1 stephen stephen 104 Oct 1 17:23 LinuxAppDev
drwxr-xr-x 1 stephen stephen 98 Sep 29 21:26 Methodics_of_LinuxAppDev
localhost%
user@localhost:~> cal | sed 's/\([01]\)\([2-3]\)/\2:\1/g'
October 22:04
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 2:1
3:1 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
user@localhost:~>
Разобьём эту часть на кусочки и разберём:
s” — замена\([01]\)\([2-3]\)” — … шаблона из двух цифр (где первая — 0 или 1, вторая — 2-3)\2:\1” — … на шаблон, где сначала стоит вторая цифра, за ней через двоеточие перваяg” — замена всех вхождений в строке