Linux_Practic_Usage

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

Немного теории про терминал

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

Терминальные картинки

Работать с компьютером вслепую было очень неудобно. Уже существовали перфокарты, которые можно было сделать заранее под каждое действие компьютера, но человеческий фактор никто не отменял, и хотелось бы при возникновении какой-то ошибки не перечитывать выколотые точки на бумажке и не искать, где какая информационная лампочка у компьютера загорелась, а вот в одно место куда-то получать ответ. А если с этого места можно будет эти самые команды компьютеру передавать вместо кучи перфокарт, так ещё и делать это прямо во время работы, жить стало бы сильно проще.

Первым терминалом стал Телетайп. Он представлял собой пишущую машинку, подключённую к компьютеру, которая печатает на бумажку текст, после чего этот текст можно было автоматически загнать в компьютер, он обрабатывался, как последовательность байт, отрабатывал команду, после чего на бумажку выводил ответ.

Это нововведение в те годы произвело невероятный скачок в работе с ЭВМ. Ничего роднее пишущей машинки для человека того времени не было. Более того, было понятно, как и эту вещь можно дальше улучшать и модифицировать. С приходом в общее пользование электронно-лучевых трубок бумажная лента заменилась на экран, но идеи, которые вложили тогда для работы через терминал, живут и актуальны до сих пор. Просто потому что лучше придумывать и не надо, всё уже максимально адаптировано.

Телетайпы стали настолько неотъемлемой частью компьютера, что современные технологии кишат отсылками на это явление:

  1. Порты в файловой системе Unix-подобных систем до сих пор называются /dev/tty как сокращение от /devices/teletypes
  2. telnet — сетевой протокол для реализации текстового терминального интерфейса по сети - также имеет своё название именно благодаря телетайпам (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:~> 

Управление через терминал

Итак, с историей всё понятно. Основная мысль: «Терминал - основа управления компьютером». Но отсюда идёт логичный вопрос: а как управлять? Просто писать команды? А если я хочу просто текст в виде команды написать, а не команду? Нужно как-то отделять, когда мы вводим просто символы, а когда управляющие команды.

Именно с такими размышлениями разработчики той самой логики работы терминала выделили некоторые байты в качестве управляющих:

Ещё одно наследие телетайпов, которое дожило до наших дней, — это подчёркивание и двойная печать. Для редактирования текста в пишущей машинке использовались возможности сдвига каретки; так, например, для подчёркивания символа каретку отгоняли назад и печатали подчёркивание поверх (получается, «под низ»?) символа; для создания жирного шрифта делали множественную повторную печать. В подсистеме просмотра руководств Linux — man — текст руководства размечается именно таким способом:

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] _|

Игры с Escape-последовательностями

Вопрос: А как? Нельзя же вывести «подчёркнутый байт»?

Ответ всё тот же: часть байтов, выводимых на терминал, воспринимаются не как символы для отображения, а как управляющие. Типичные пример — всё те же перевод строки (который опускает курсор — позицию «каретки телетайпа» — на строку ниже) и возврат каретки (который возвращает курсор в начало той же строки). Правда, терминал в обрабатываемом режиме заменяет перевод строки на оба этих символа, так что включим в примере режим 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

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 существуют спецсимволы перенаправления:

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:~>

Условный оператор (if-fi)

\(\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

Цикл

  1. Цикл с условием:

    \[\textrm{while\ \textbf{команды};\ do\ \textbf{команды};\ done}\]
  2. Цикл по последовательности:

    \[\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 для просмотра содержимого файла. Поскольку их работа более-менее понятна всем, рассмотрим специализированные программы для работы с файлами и файловой системой.

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%
localhost% cat > file
Hello my dear friend
localhost% cut -c 7-15 file
my dear f
localhost% cut -d ' ' -f 2 file
my
localhost%
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%

Комбайны

Комбайны — важный класс утилит, взаимодействующий со входным потоком на основе регулярных выражений.

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:~>

Разобьём эту часть на кусочки и разберём: