Product SiteDocumentation Site

Глава 5. Работа со сценариями

5.1. Unix Shell Package
5.2. Gear by ALT Linux Team

5.1. Unix Shell Package

При разработке пакета для универсальной работы с внешними программами используются языки склейки. Это универсальный интерфейс управления системы, позволяющий использовать встроенные в систему программы.
В качестве базового набора используются команды Unix Shell. Соберём пакет с приложением, написанном на языке склейки:
@builder: RPM/SOURCE/todo-pkg-1.0.sh
#!/bin/bash

WORKDIR=$HOME/.config/shell-pkg
TODOLIST=$WORKDIR/todo-list
ANSWER_FILE=`mktemp --suffix=-shell-pkg`

exit_handler() { trap - EXIT; rm -f "$ANSWER_FILE"; }
trap exit_handler EXIT HUP INT QUIT PIPE TERM

mkdir -p $WORKDIR
test -r $TODOLIST || touch $TODOLIST
TODOCOUNT=`wc -l < $TODOLIST`

Auto_screensize() {
	eval `dialog --print-maxsize --stdout | sed -E 's/.* (.*), (.*)/W=\1; H=\2; WW=$((W-10)); HH=$((H-10))/'`
}

Menu() {
	Auto_screensize
	if dialog --title ShellPkg --ok-label "Choose" --cancel-label "Exit" \
		--menu "" $WW $HH 3 \
		Show_todo "Todo list" \
		Add_todo "Add TODO" \
		Solve_todo "Solve TODO" \
		2> "$ANSWER_FILE"
	then
		read answer < "$ANSWER_FILE"
		$answer
	else
		return -1
	fi
}

Add_todo() {
	Auto_screensize
	if dialog --inputbox "Please write your TODO" $WW $HH 2> "$ANSWER_FILE"
	then
		read answer < "$ANSWER_FILE"
		((TODOCOUNT++))
		echo "$TODOCOUNT NEW $answer" >> $TODOLIST
	fi
}

Show_todo() {
	solved_todo="Solved TODO:\n"
	unsolved_todo="Unsolved TODO:\n"

	while read number status todo; do
		if [ $status = "NEW" ]; then
			unsolved_todo="$unsolved_todo - $todo\n"
		else
			solved_todo="$solved_todo - $todo\n"
		fi
	done < "$TODOLIST"

	Auto_screensize
	dialog --title "List of all your TODO" --msgbox "$solved_todo$unsolved_todo" $WW $HH
}


Solve_todo() {
	unsolved_todo=""
	count=0
	while read number status todo; do
		if [ $status = "NEW" ]; then
			unsolved_todo="$unsolved_todo $number ${todo// / } off"
			((count++))
		fi
	done < "$TODOLIST"

	Auto_screensize
	if dialog --title "Mark solved TOSOs" \
		   --checklist "" $WW $HH $count $unsolved_todo \
		   2> "$ANSWER_FILE"
	then
		read answer < "$ANSWER_FILE"
		for num in $answer; do
		sed -i -E "s/^($num) NEW/\\1 DONE/" "$TODOLIST"
		done
	fi
}

while Menu; do :; done
Подробнее рассмотрим некоторые конструкции программы:
  • для удобства работы определены функции:
    Menu() {
    	Auto_screensize
    	if dialog --title ShellPkg --ok-label "Choose" --cancel-label "Exit" \
    		--menu "" $WW $HH 3 \
    		Show_todo "Todo list" \
    		Add_todo "Add TODO" \
    		Solve_todo "Solve TODO" \
    		2< "$ANSWER_FILE"
    	then
    		read answer < "$ANSWER_FILE"
    		$answer
    	else
    		return -1
    	fi
    }
    
  • для проверки атрибутов используются условные конструкции if-then-else-fi, для описания условий проверки используется сокращённый макрос оператора test —  [;
  • нам пришлось пойти на маленькую хитрость. Подстановка получившегося текста в $unsolved_todo разбивает этот текст на отдельные слова, и если в $todo были пробелы, dialog работает не так, как ожидалось. Простое добавление кавычек не помогает. Поэтому необходимо заменить пробел на неразрывный пробел (символ с кодом 0xa0c2), который отображается так же, но shell не считает его разделителем;
    while read number status todo; do
    	if [ $status = "NEW" ]; then
    		unsolved_todo="$unsolved_todo $number ${todo// / } off"
    		((count++))
    	fi
    done < "$TODOLIST"
    
  • основное управление осуществляется через меню с помощью цикла while:
    while Menu; do :; done
    
  • псевдографический интерфейс обеспечивается с помощью утилиты dialog;
    Auto_screensize() {
    	eval `dialog --print-maxsize --stdout | sed -E 's/.* (.*), (.*)/W=\1; H=\2; WW=$((W-10)); HH=$((H-10))/'`
    }
    
    <...>
    
    dialog --title "List of all your TODO" --msgbox "$solved_todo$unsolved_todo" $WW $HH
    
  • для UI dialog использует Ncurses, так что весь управляющий вывод (например, метка выбранного пункта меню) выводится в другой дескриптор (2, то есть stderr) и перенаправляется в файл;
  • файл временный, заводится при старте с помощью утилиты mktemp, она же обеспечивает ему уникальность имени;
  • удаляется файл по окончании работы сценария.
Теперь разберём spec-файл:
@builder: RPM/SPECS/todo-pkg.spec
Name: todo-pkg
Version: 1.0
Release: alt1

Summary: Terminal TODO-list

License: GPL-3.0-or-later
Group: Development/Other

Source: %name-%version.sh

%description
Application Add and solve your TODO-s in this app

%install
install -D %SOURCE0 %buildroot%_bindir/%name

%files
%_bindir/*

%changelog
* Wed Jul 09 2025 UsamG1t <usamg1t@altlinux.org> 1.0-alt1
- Initial Build
Заметим, что сборочных зависимостей у пакета нет, но будут эксплуатационные — пакет dialog, необходимый для отрисовки интерфейса. Автоматический поиск зависимостей, срабатывающий при сборке пакета, позволяет явно не указывать в spec-файле данный пакет:
@builder
[builder@localhost ~]$ tree -A RPM
RPM
├── BUILD
├── RPMS
│   └── noarch
├── SOURCES
│   └── todo-pkg-1.0.sh
├── SPECS
│   └── todo-pkg.spec
└── SRPMS

7 directories, 2 files
[builder@localhost ~]$ rpmbuild -ba RPM/SPECS/todo-pkg.spec
Executing(%install): /bin/sh -e /usr/src/tmp/rpm-tmp.96219
<...>
Finding Requires (using /usr/lib/rpm/find-requires)
...
find-requires: FINDPACKAGE-COMMANDS: dialog mkdir rm sed touch
Requires: /bin/bash, /etc/bashrc, coreutils, dialog, sed
<...>
Wrote: /usr/src/RPM/SRPMS/todo-pkg-1.0-alt1.src.rpm (w2.lzdio)
Wrote: /usr/src/RPM/RPMS/x86_64/todo-pkg-1.0-alt1.x86_64.rpm (w2.lzdio)
[builder@localhost ~]$
Поскольку зависимость эксплуатационная, сборка пакета не требует наличия dialog, однако при установке появится предупреждение:
@rooter
[root@localhost .in]# rpm -i todo-pkg-1.0-alt1.x86_64.rpm
error: Failed dependencies:
       dialog is needed by todo-pkg-1.0-alt1.x86_64
[root@localhost .in]# rpmquery --requires --package todo-pkg-1.0-alt1.x86_64.rpm
/bin/bash
/etc/bashrc
coreutils
dialog
sed
rpmlib(PayloadIsLzma)
[root@localhost .in]#
@user
[user@VM ~]$ hsh-install dialog
<13>Jul 17 08:04:33 rpmi: libdialog-1.3.20171209-alt2 sisyphus+328094.100.1.1 1693228848 installed
<13>Jul 17 08:04:33 rpmi: dialog-1.3.20171209-alt2 sisyphus+328094.100.1.1 1693228848 installed
[user@VM ~]$ cp todo-pkg-1.0-alt1.x86_64.rpm hasher/chroot/.in
[user@VM ~]$ hsh-shell --rooter
@rooter
[root@localhost .in]# rpm -i todo-pkg-1.0-alt1.x86_64.rpm
<13>Jul 17 08:04:56 rpm: todo-pkg-1.0-alt1 1752738978 installed
[root@localhost .in]#