akkerman
Рейтинг
+10.10
Сила
19.33

akkerman

Ганц Аккерман

Если пробел встал у вас на пути. Как правильно перейти в каталог (рас|место)положения скрипта?

  • BASH
Вы всё ещё делаете это так?


cd $(dirname $(readlink -e $0))


Это ленивый вариант, который работает «в большинстве случаев». Однако же он не является верным, поскольку пути (пусть это не покажется вам странным) в общем случае могут содержать пробельные символы.

Надо бы вот так:

cd "$(eval "readlink -e '$0'" | sed -r -e 's%(^|/)[^/]+$%%' -e 's%([^/])/+$%\1%')"


Попробуйте — это просто работает как надо, сколько бы ни встретилось пробелов на тернистом пути вашего скрипта.

Inline assembler в Crystal

До меня доходили слухи, о том, что якобы в Crystal нет inline ассемблера.
Однако же это не так.


def add(a, b)
    asm("
        movl ($0), %eax
        addl ($1), %eax
        movl %eax, ($0)
    " :: "r"(pointerof(a)),"r"(pointerof(b)))
    a
end

x=12_i32
y=14_i32

puts add(x,y)


Есть только одна проблема: в AT&T ассемблере символ "$" вообще-то используется в качестве отличительного_префикса/сигила для констант. А здесь, как нетрудно заметить, $N — это ни что иное, как позиционный аргумент в блоке asm. И да, не спрашивайте меня, что ещё «r»(a) и странное "::" — сам теряюсь в догадках, но очевидно, что :: отделяет аргументы от кода в блоке asm, а «r»(something) собственно отвечает за определение позиционных $N, каждый из которых будет равен своему something. Сакральное знание о том, как использовать блоки asm, почёрпнуто мной отсюда: github.com/crystal-lang/crystal/blob/e2a1389e8165fb097c785524337d7bb7b550a086/src/fiber.cr#L10

Как считать данные из объекта IO в структуру данных?

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

Самой большой проблемой является то, что данные из IO-объектов могут удобным образом считываться только в типы данных, представленные классам, корректно реализующими некий from_io метод. Такого рода заморочки для решения элементарной и очевидной задачи мне показались некоторым перегибом, а поскольку сопутствующего головокружения от успехов у меня не наступало, я решил поискать другой способ.

Вот, например: можно считать данные из объекта IO (в моём случае — сокета) в некий инстанс типа Bytes. Изучаем API языка — и узнаём о том, что Bytes — это ни что иное как Slice(UInt8). Ну легче конечно не стало, но продолжаем бороздить космические глубины — и натыкаемся на то, что Slice — это вообще-то разновидность указателя Pointer, только с жёстко привязанной к нему логикой проверки границ. Узнав о таких перипетиях бытия, я решил было, что смогу сделать Slice, указывающий на экземпляр, он же инстанс, моей гипотетической структуры данных, а потом просто записать в него данные их сокета. Но конечно же не тут-то было: считать-то можно, но только в Slice(UInt8), если это Slice(C::SomeStruct), например, то Crystal посетует на отсутствие подходящего метода для IO — и откажется компилировать программу. Но лиха беда начало, добрался я и до «родительского» по отношению к Slice классу Pointer. А тот, как назло, тоже строго привязан к типу (при создании нужно указывать тип данных) и порождает, соответственно, Slice(T), где T — это тот же тип, что и в порождающем Pointer'е. Думал я думал — и придумал, что раз я не могу просто создать Pointer на экземпляр структуры данных, куда следует записать данные из IO, то я ведь могу создать 2 указателя: один на экземпляр структуры, а другой, вспомогательный, на UInt8. И это оказалось верным решением, потому что Pointer, к счастью огромному, при создании не требует сразу указывать нечто определённое с присущим этому определённому типом T. Оказывается, указатель даже на Crystal можно создать просто под адресу, т.е. указав некое смещение в памяти. Ну а что же в таком случае мешает получить какое угодно количество указателей, формально относящихся к произвольным типам данных и указывающих, например, на одно и то же место в памяти? Да ничто не мешает!

Осознав всё сказанное выше, я реализовал запись из сокета в структуру данных буквально вот так, просто и элегатно:


lib C
   struct ZbxSenderHdr
     z_sign : StaticArray(UInt8, 4)
     z_stop_byte : UInt8
     z_payload_l : UInt8
   end
end
zhdr=C::ZbxSenderHdr.new
sock.read(Slice.new(Pointer(UInt8).new(pointerof(zhdr).address), sizeof(C::ZbxSenderHdr)))


Т.е. я создал Slice из Pointer(Uint8), который в действительности указывает туда же, куда и pointerof(zhdr) — и записал по адресу Slice'а данные, считанные из сокета.

Не знаю уж, насколько всё это может быть наглядным, но надеюсь, что кому-то кроме меня может оказаться полезным :)

Засим позвольте откланяться — и до новых встреч!

Событийно-ориентированное программирование: простой пример

Даже очень простой и очень неполный: хотим мы, например, получить пользовательский ввод, как-то его обработать в callback'е и получить после того, как всё-таки пользователь соизволит что-то ввести — некий результат на базе введённых данных.

Хотя в примере это не показано (но будет показано в дальнейших примерах, это уж будьте спокойны на сей счёт), но в то самое время, пока пользователь что-то неспеша тупит и вводит — мы можем преспокойно выполнять ещё тысячи зелёных потоков, которые сделают что-нибудь разумное, доброе и вечное. Например, разогреют мышку пользователя до 150 градусов по Цельсию или нервно помигают светодиодами игровой клавиатуры Why not? :)

Итак, вот вам piece-of-code:
class AnyEvent
    def initialize
    end
    def input(&block : String -> Int32)
        promise=Channel(Int32).new
        spawn do
            s=""
            while s.empty?
                print "Press any keys, then hit Enter -> "
                s=gets || ""
            end
            promise.send(block.call(s))
        end
        promise
    end
end

wait_event=AnyEvent.new
input_result=wait_event.input do |keys_was_pressed|
    puts "String is #{keys_was_pressed}"
    keys_was_pressed.size
end
puts "I've received #{input_result.receive} characters as an answer. Thank you SO much! :)"


Обдумывайте.

И счастливых выходных!

Идеальный PS4

  • BASH
Нашёл я как-то на просторах интернет интереснейший вариант наполнения префикса PS4 полезным содержимым. Считаю необходимым сделать эту информацию достоянием широкой общественности, а посему спешу поделиться ею со своими читателями, дабы и они тоже смогли проникнуться всей силой и мощью set -x:


export PS4='+(${BASH_SOURCE}: #${LINENO}): ${FUNCNAME[0]:-main}(): '


Напомню: PS4 — это префиксный кусок текста, добавляемый в каждую интерполированную строку, которую BASH выводит в режиме отладки/трассировки, включаемый по команде set -x и отключаемый по команде set +x соответственно.

Быстрой вам отладки и доброго продакшна!

Рецепт: как подождать завершения всех порождённых потоков исполнения

И очень просто: нужно в основном (порождающем) потоке подождать получения данных на «вещательном» канале столько же раз, сколько «вещающих» в этот канал потоков мы породили.
Это чем-то похоже на телефонную конференцию: вы ждёте, пока все остальные участники попрощаются (при этом можете не вдаваться в подробности того, кто именно отсалютовал) — и вешаете трубку.

Собственно код:

begin
	ch=Channel(Int32).new
	fibCount=0
	i : Int32
	10.times{|i|
		fibCount+=1
		spawn do
			r=Random.new
			nsec=Random.rand*5.0
			puts "№#{i} launched, will sleep #{nsec}"
			sleep nsec
			puts "№#{i} sleeped #{nsec}, will send its ID to channel"
			ch.send(i)
			puts "№#{i} sended its name to channel, now finishing..."
		end
	}
	
	fibCount.times{
		i=ch.receive
		puts "main: received message from fiber №#{i}"
	}
	
	puts "main: sleeping 1 second"
	sleep 1
	puts "main: Congratulations! Thats DONE!"
rescue ex
	puts "EXCEPTION: #{ex.message}"
end

Аз, Буки, Веди! Читаем конфиги

Все мы рано или поздно читаем конфиги. Даже если начинаем с прописанных где-то в начале тела программы статических переменных, содержащих что-нибудь неизменное наподобие логина и пароля и даже если потом в силу давления обстоятельств, требующих от нас максимальной гибкости, начинаем использовать ключи командной строки (если это вообще возможно) — вот ну точно вам говорю, рано или поздно от чтения конфига всё равно никуда не деться.

Как-то и мне понадобилось нечто подобное… а потом ещё миллион раз :) И приступая к изучению нового для себя языка программирования, каковым для меня пока является и Crystal, я неизменно возвращаюсь к одному и тому же вводному вопросу: а как бы здесь прочитать конфиг в моём любимом простейшем BASH-совместимом формате (том, который легко подключается source'м из BASH-скрииптов): переменная=«зна 'че' ние» или переменная='зна «че» ние' или даже переменная=значение?

Для Crystal я тоже написал подобный пример. Вот он:


begin
        raise "Hey! You must specify file name as a first parameter" if ARGV.size==0
        rows=File.read_lines(ARGV[0])
        rows.reject! { |line| line =~ /^\s*(?:#.*)?$/ }
        conf=rows.map{ |line|
                if
                 line.match(/^([^#=\s]+)\s*=\s*(?:(?<Q>["'`])((?:(?!\k<Q>|\\).|\\.)*)\k<Q>)(?:\s+(?:#.*)?)?$/)
                        [$1,$3]
                elsif line.match(/^([^#=\s]+)\s*=\s*([^#\s"']+)(?:\s+(?:#.*)?)?/)
                        [$1,$2]
                else
                        raise "invalid-formatted config line: #{line}"
                end
        }.to_h

        p conf
rescue ex
        puts ex.message
        exit(1)
end


Поясню:

в секции rescuе обрабатываются любые возникающие исключения, в числе которых и ошибка открытия файла

ARGV[0] содержит первую опцию командной строки

Метод reject! изменяет список, являющийся экземпляром класса Array, так, чтобы в нём не осталось пустых строк и комментариев.

Метод map на том же самом rows возвращает «список списков», т.е. список, состоящий из элементов, каждый из которых представляет собой этакий крохотулечный «списочек» всего из 2-х элементов. В первый из элементов каждого такого «списочка» благодаря «усилиям» оператора match и скобкам в регулярном выражении — попадает переменная из конфига, а во второй элемент (благодаря всё тем же действующим лицам) — значение для переменной.

to_h преобразует наш список списков… во что бы вы думали? Да, точно, в Hash-массив, в котором каждый «списочек» магическим образом превращается в пару ключ-значение.

«p conf» — это на самом деле такой отладочный «хак» языка Crystal: инструкция p попросту выводит «дамп» любого переданного ей объекта. В данном случае — дамп Hash'а.

В реальной программе вы бы конечно использовали conf неким полезным образом. Ну а конфиг с паролями у вас конечно же защищён правами доступа «rw-------», оно же 600? Вот и молодцы!

Сохраняйте код в файл, запускайте «crystal your_funny_code.cr имя_файла» (обратите внимание на собственное расширение для языка Crystal) — и, как говорят, enjoy!

А получить удовольствие есть от чего: ведь вы написали настоящий компилируемый код — и обошлись без груды закорючек, спецификаций типов и возни с выделением/освобождением памяти :)

Потоки исполнения как инструмент опытного "повара"

Ещё раз прочитайте внимательно вопрос в заголовке. Прочитали? А теперь без всякого промедления правильный ответ:

Потоки или нити — куски кода, использующие одни и те же данные в памяти, исполняемые процессором компьютера попеременно, либо одновременно, либо и так, и так.

Читать дальше →

Как собрать в архив все файлы rpm-пакета?

  • BASH
В продолжение аналогичного поста о dpkg предлагаю использовать следующий «наивный» вариант скрипта, собирающего те файлы, которые были скопированы в систему при установке rpm-пакета. Файлы, созданные автоматически INSTALL-скриптом пакета при этом «подобраны» не будут.

Для запуска скрипта нужно иметь возможность выполнять sudo под суперпользователем: это связано с тем, что немалая часть пакетов устанавливает файлы с эксклюзивным доступом к ним root'а: в таких случаях попытка создать tar-архив провалиться с треском.

Собственно код:

#!/bin/bash
[[ $1 == '-x' ]] && { shift; export TRACE=1; set -x; }
[[ $1 ]] || { echo 'You must specify package name or path to some file installed from the package' >&2; exit 1; }

if [[ ${1:0:1} == '/' ]]; then
        FILE=$1
        if ! PACKAGE=$(rpm -qf $(readlink -e $FILE)) 2>/dev/null || ! [[ $PACKAGE ]]; then
                echo 'Cant determine package name by file name' >&2
                exit 2
        fi
else
        PACKAGE=$1
        if ! PACKAGE=$(rpm -q $PACKAGE); then
                echo "This package seems to be not installed" >&2
                exit 3
        fi
fi

sudo bash <<EOSCRIPT
        tar -cjf "/tmp/${PACKAGE}.tbz2" -T <(
          while read f; do
                  [[ -d \$f ]] || echo "\$f"
          done < <(rpm -ql '${PACKAGE}')
        )
        chown "$(whoami)":"$(id -gn)" "/tmp/${PACKAGE}.tbz2"
        echo "/tmp/${PACKAGE}.tbz2 created" >&2 
EOSCRIPT


UPD: Поправил код скрипта, избавив его от 2-х вызовов sudo и пофиксив ошибки отсутствия присвавивания FILE=$1, а также ошибку сохранения короткого имени пакета, если было передано именно таковое.

Однострочник: получить дамп заголовков HTTP-трафика на первом ethernet-интерфейсе

Некоторые однострочники просто поражают своей лаконичностью, но могут быть не вполне функциональны.
Некоторые — написаны откровенно неэстетично и содержимое их вызывает блевотный рефлекс, но когда читать man'ы лениво — бегло сканируешь незамыленным глазом чей-то шедевр — и копируешь как есть, потому что… ну да, они «просто делают свою работу» (прямо как офисный планктон в Москве: просто делает что-то… как-то).
А есть однострочники, которые вроде и полезны весьма условно, но представляют интерес чисто академический: хочется разобрать их на винтики и понять, как же хиромантия сия устроена, что за магия заставляет вращаться чудесные шестерёнки Unix-way.
В качестве наглядного примера представляю на суд моих благодарных читателей (кстати, в последнее время я был безмерно счастлив отвечать на письма зело многочисленных поклонников моего скромного творчества) — вот этот многострочник, составленный из двух частей: одну легко найти на StackOverflow, а вторую приписал Ваш Покорный Слуга (здесь и далее — ВПС).

Вот:

sudo tcpdump -i $(ifconfig | sed -nr '/^[^[:space:]]/{ s%^([^[:space:]:]+).*$%\1%; h; b X}; /^\s*ether/{ g; p; q }; :X') -A -s 10240 'tcp port 3128 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' | egrep --line-buffered "^........(GET |HTTP\/|POST |HEAD )|^[A-Za-z0-9-]+: " | sed -r 's/^........(GET |HTTP\/|POST |HEAD )/\n\1/g'


Те истинные герои Совнгарда, которые первыми осознают и прочувствуют Суть работы механизмов радости в контексте представленного выше примера — да не сочтут они за труд отписаться в комментариях к посту, дабы подвиг их не канул в Лету.

Аминь!

P.S. Если вы низвергаетесь в грешный мир этих наших интернетов не через SQUID на дефолтном порту 3128, то замените циферки 3128 на что-то более другое. Спасибо!

Do You Know That? Как раскрывается "${array[@]}" и "${array[*]}"

  • BASH
Оказывается, в зависимости от режима интерполяции, внутри двойных кавычек массивы раскрываются принципиально по разному:

  • В случае с "${array[*]}" — массив интерполируется в один аргумент (одну строку), представляющий собой результат простой конкатенации всех элементов массива через пробел
  • В случае "${array[@]}" — каждый элемент массива становится отдельным аргументом так, словно каждый элемент взял себе внешние двойные кавычки, что уберегло его от дробления по пробельному символу (точнее, по IFS)

Тестовый пример:

Читать дальше →

Вдогонку к Tips&Tricks'у #10h

  • BASH
Конечно же, для функции join2 должна быть и комплементарная split.

Учитывая то, что просто split — это «split a file into pieces», а также принимая во внимание тот факт, что split-функция всё-таки будет писать в переменную-массив, а не просто на STDOUT, я добавил к привычному имени циферку 2, дабы получилось у нас «SplitTo (the) arrayName».

Ну а теперь код:

split2 () {
        local arrName=$1
        [[ $arrName =~ ^[0-9_A-Za-z]+$ ]] || return 1
        local delim=$2
        shift 2
        local args
        while (( $# )); do
         args+=${args:+$delim}$1
         shift
        done  
        readarray -t $arrName < <(echo -n "${args//$delim/$'\x0a'}")
        return $?
}


Пример использования:

split2 dir '/' '/usr/share/doc/LaTeX' 'a/b' 'c/d/e f g/h'
declare -p dir
# OUTPUT:
# declare -a dir='([0]="" [1]="usr" [2]="share" [3]="doc" [4]="LaTeX" [5]="a" [6]="b" [7]="c" [8]="d" [9]="e f g" [10]="h")'


Enjoy! :)

BASH Tips&Tricks #0010: И на BASH'е будут join'иться массивы!

  • BASH
Когда я пишу на Perl, у меня буквально всё под рукой. Кое-что даже мешается и заставляет постоянно проверять кусочки кода в командной строке (perl -e или perl -E), дабы сделать полёт моей фантазии более комфортным и безопасным для здоровья разрабатываемой софтины.

Когда я пишу на BASH, мне, конечно же, многого не хватает: необъятные возможности работы с текстом в Perl'е накладывают свой отпечаток на отношение к другим языкам. Кое-что приходится терпеть, но многое вполне можно исправить, предоставив самому себе привычное окружение, пусть и несколько… задумчивое («бытует мнение», что в случае с BASH скоростью работы обычно можно пренебречь).

Так случилось и с функцией join, которой мне всегда так не хватало в BASH. Я, конечно же, говорю не о той join, которая «join lines of two files on a common field», а об одноимённой функции Perl, «склеивающей» элементы массива (если быть точным, то всё-таки «вектора») в строку.

Я долго терпел сие неудобство и всячески его игнорировал, но… в какой-то момент терпение моё лопнуло, и я решился на сотворение мира в атомарных масштаба. По итогам краткого ознакомления с бесплодными исканиями на StackOverflow и захватывающе познавательного обсуждения join-вопросов с умными людьми © на linux.org.ru, мною был исторгнут приведённый ниже код:

join2 () {
 (( $# == 2 || $# == 3 )) || return 1
 local delim=$1
 local arrName=$2
 local join2var=$3
 local v
 for v in ${join2var:+join2var} arrName; do
  [[ ${!v} =~ ^[_0-9A-Za-z]+$ ]] || return 2
 done
 [[ $join2var ]] || join2var='v'
 source <(
        cat <<SOURCE
         printf -v $join2var "%s${delim}" "\${$arrName[@]}" || return \$?
         [[ \$delim ]] && $join2var=\${$join2var:0:-${#delim}}
SOURCE
 )
 (( $# == 2 )) && echo "$v"
 return 0
}


Бесспорно, он (код) как всегда великолепен и безупречен… пока мною же не будет доказано обратное :)

Квинтэссенция сути функции join2 (звучит как «Join To») заключается в том, что встроенная команда BASH printf понимает конструкцию ${arr[@]} не как уже интерполированную строку из элементов массива, объединённых абы чем (пробелами), но как именно набор элементов. Что эта белиберда значит? А то, что если какой-либо элемент массива уже содержит пробелы, а его объединят пробелом с элементом соседним, printf всё равно «увидит» каждый элемент в отдельности.

Пользоваться join2 можно двояко:
  1. 
    declare -a arr=('a b c' 'd e f' 'g h i')
    join2 '///' arr
    

    — и в этом случае результат конкатенации элементов массива будет «выведен на экран», то есть отправлен на STDOUT, где его легко подобрать так:
    str=$(join2  '///' arr)


    ИЛИ… (барабанная дробь, фанфары!)
  2. 
    declare -a arr=('a b c' 'd e f' 'g h i')
    join2 '///' arr str
    echo "$str"
    

    — а в этом случае название функции окажется весьма неслучайным, поскольку результат join'а запишется сразу в переменную str.

Cакральное знание об особенностях работы printf с массивами в BASH нагло сп скромно почёрпнуто из Yet Another BASH FAQ'а.

Пример преобразования форматов на BASH 4

  • BASH
Если кто-нибудь скажет Вам, что BASH — это какой-то недостойный внимания недоязык для написания циклов из командочек, киньте ему ссылку на данный пример.

Пример совсем несложный, я только что написал его для StackOverflow (где, к сожалению, люди всё ещё живут в криогенных камерах, не ведая о существовании BASH 4-й версии):

#!/bin/bash
inFile=$1
outFile=$2

join () {
 local del=$1
 shift
 IFS="$del"
 source <(
        cat <<SOURCE
 echo "\${$1[*]}"
SOURCE
 ) 
 unset IFS
}

declare -a CSV=('"Module Name","Module Group","Module Version"')
declare -a keysAccepted=('Name' 'Group' 'Version')

declare -i nMandatoryKeys=${#keysAccepted[@]}
declare -A KeyFilled
rxKeysAccepted='('$(join '|' keysAccepted)')'
while read line; do
        [[ $line =~ \<strong\>Module\ $rxKeysAccepted:\</strong\>[[:space:]]*([^<]+)\</p\> ]] || continue
        key=${BASH_REMATCH[1]}
        val=${BASH_REMATCH[2]}
        KeyFilled[$key]=$val
        if (( ${#KeyFilled[@]} == nMandatoryKeys )); then
                unset csvLine
                for k in ${keysAccepted[@]}; do
                        csvLine+=${csvLine:+,}${KeyFilled[$k]}
                done
                KeyFilled=()
                CSV+=($csvLine)
        fi
done <"$inFile"

(( ${#CSV[@]} > 1 )) || exit 1

join $'\x0a' CSV >"$outFile"


BASH-скрипт состоит из sed'ов, awk и grep'ов?
Давайте подсчитаем количество вхождений того, другого и третьего в данном примере!

Не спорю, тот же самый код на Perl, лучшем языке для работы с текстами, получился бы и короче намного, и быстрее.

Но, например, не нагляднее (Perl немного провоцирует писать супер-лаконичную абракадарбру). А является ли свойством BASH как языка программирования его действительно порой раздражающая медлительность — это ещё большой вопрос.

Do You Know That? Особенности mkdir -p

Утилита mkdir из GNU coreutils (во всех Linux) отличается следующими особенностями работы в режиме создания иерархии каталогов «на лету»:

  1. Если целевой каталог существует — mkdir с ключом -p не только не сообщит Вам об этом, но и не вернёт код ошибки
  2. Если добавить ключ -v (verbose) впридачу к -p — mkdir сообщит на STDOUT о каждом созданном ею каталоге. Именно по отсутствию какого-либо вывода в режиме -vp легко понять, что целевой каталог уже существовал на момент попытки его создания
  3. Всем создаваемым каталогам, кроме целевого, будет присвоена маска доступа, установленная umask (или действующая по умолчанию), а не та, которую Вы укажете в качестве значения для ключа -m утилиты mkdir/ Тем не менее, если Вы всё же укажете маску значением ключа -m, она будет использована при создании целевого каталога

Получение информации о процессах в стиле BASH 4

  • BASH
При написании BASH-скриптов распространённой практикой является считывание любой информации о процессах с помощью утилиты ps в сочетании с awk. В большинстве случаев это делается не ради кроссплатформенности (ps весьма ограниченно кроссплатформенна), а просто в силу непонимания возможностей BASH и принципиального отсутствия желания писать на нём более-менее «традиционный» код вместо нагромождения pipe'ов.

В данном посте я покажу, что код на BASH может оперировать вполне «традиционными» собственными структурами данных — и при этом с минимальным использованием внешних утилит.

Читать дальше →

Как собрать все файлы deb-пакета в tar-архив?

  • BASH
Если у вас есть устанволенный deb-пакет и хочется аккуратно собрать все его файлы в архив TAR, то сделать это можно вот так:


PACKAGE='libmysqlclient-dev'
tar -cjf /tmp/$PACKAGE.tbz2 -T <(while read f; do [[ -d $f ]] || echo "$f"; done < <(dpkg -L $PACKAGE))


Бывает, что права root'а недоступны, пакеты deb вы нормальным образом установить не сможете (в том числе и любые инструменты сборки из исходников) и тогда подобный «перенос в tar-архивах» становится единственной возможностью для работы в сильно стеснённых условиях. При этом если вы оставите все пересённые tar-архивы на целевой системе, то сможете с помощью опции -t получить список файлов архива и, соответственно, при необходимости вычистить ненужное.

Этакая эмуляция «пакетной системы» Slackware получается :)

P.S.
Думаю, несложно догадаться, что приведённый ниже скрипт соберёт в архив все файлы, установленные из rpm-пакета, в котором был /usr/sbin/zabbix_proxy:


FILE=/usr/sbin/zabbix_proxy
PACKAGE=$(rpm -qf $(readlink -e $FILE))
[[ $? == 0 && $PACKAGE ]] && \
    sudo bash -c 'tar -cjf /tmp/'$PACKAGE'.tbz2 -T <(while read f; do [[ -d $f ]] || echo "$f"; done < <(rpm -ql '$PACKAGE')); echo "/tmp/'$PACKAGE'.tbz2 created"'

Microhint: Загрузка модуля OpenLDAP требует 2 файла, а не 1!

  • LDAP
Модули, содержащиеся в contrib исходных кодов openldap'а (и любые другие), требуют для своей загрузки не только файл с расширением ".la", но и соответствующий файл с расширением ".so", лежащий в одном каталоге с ".la".

Проблема может возникнуть из-за того, что неторопливсые люди наподобие меня могут получить в результате простого make'а соответствующего модуля файл с расширением ".la", скопировать его в каталог olcModulePath, радостно попытаться модуль подгрузить olcModuleLoad'ом и… получить «file not found» в логах, который по какому-то глупому недоразумению разработчиков OpenLDAP ссылается не на нехватку ".so", а на якобы отсутствие ".la". Но вы-то знаете, что этот файл на самом деле есть!
А в действительности файлик с расширением ".so" создаётся make install'ом, и без него модуль грузится не будет.

Ларчик как всегда просто открывался, но на будущее нужно запомнить :)