При разработке пакета для универсальной работы с внешними программами используются
языки склейки. Это универсальный интерфейс управления системы, позволяющий использовать встроенные в систему программы.
В качестве базового набора используются команды 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]#