LINUX.ORG.RU

Хочу говорить про Bash

 


2

4

Для себя уже навалял кучку полезных скриптиков и походу изучения bash периодически их перечитываю, и что-то там допиливаю, но понял, что надо что-то посложнее попробовать и этакое поближе к программированию.

Решил попробовать написать программку, которая удаляет дубликаты строк из .bash_history (там более 7000 строк у меня). Мне это показалось достойной задачкой для начинающего башиста, хотя и (может быть) довольно бесполезной. Кстати, есть такая программа shell-history-cleaner (кажется на расте написана, давно себе собрал, работает, но видимо заброшена автором), я пользуюсь, но попробовать свои силы надо было.

В результате мне удалось сделать задуманное, но хочется поговорить и о «неудачных» вариантах, которые может быть были бы более удачными, если бы я больше знал о bash и linux вообще, так что категорически приветствуется критика и подсказки более правильных решений или каких-нибудь хитростей командной строки.

Так как-то сложилось, что я упорно откладывал изучение массивов на потом, и когда наконец более-менее разобрался с индексированным массивом, то сразу приступил к делу, чтобы заодно закрепить прочитанное в книжках, поэтому первый «неудачный» вариант будет об этом.

Программа рабочая, делает дело, но долго. Мой файл перемалывает за минуту с небольшим, при этом нагружая одно виртуальное ядро процессора на 100%. Зная лоровские нарративы об ущербности bash как языка программирования, я подумал, что вот и столкнулся с подобной ущербностью, поэтому просто искал возможность хоть как-то оптимизировать процесс.

Я не знаю никаких алгоритмов, так что рассудил так: надо взять (последнюю) строку, удалить дубликаты, строку положить в файл и так по кругу.

file=$1
mapfile -t list < <(grep -v '^$' $file)
while ((${#list[*]})); do
    line=${list[-1]}
    unset list[-1\]
    for i in ${!list[*]}; do
        [[ $line == "${list[i]}" ]] &&
            unset list[i\]
    done
    final+=("$line")
done
printf '%s\n' "${final[@]}" | tac > $file

Для таких же любителей как я поясню идею кода. На его краткость отлично повлиял тот факт, что в истории командной строки надо сохранять последние уникальные строки, то есть работать надо с конца файла, а у индексированных массивов как раз есть стабильная ссылка на последний индекс: array[-1]. То есть не надо ничего переворачивать.

Как я понимаю, самое тугое место в скрипте — сравнение строк, и что-то более быстрых вариантов походу нет. Далее надо думать о многопоточности, но я пока до этого не дозрел.

Поскольку на данном этапе я застрял и вроде бы ясно, что тема с массивами как-то всё усложняет, то решил написать скриптик в стиле unix-way, и искренне полагал, что это будет правильным решением задачки, ведь утилиты все написаны суровыми дядьками на Си, и там всё без дураков — быстро и надёжно. Пришла идея отбросить всю эту возню с поиском и удалением, а просто взять всё — и отфильтровать.

file=$1
list=$(tac $file | grep -v '^$')
while [[ $list ]]; do
    read -r line <<< $list
    list=$(grep -Fxv "$line" <<< $list)
    echo "$line"
done | tac > $file

Чтобы уважаемый лорчанин не хельпал ключи -F -x, напомню: F — читает regex буквально, а x — помещает выражение между ^$, иначе пришлось бы использовать ключ -P (perlre) и выражение выглядело бы как говно: "^\Q$line\E$", а работало бы ещё медленней, возможно, но это не точно. (кстати, я был весьма разочарован узнать, что с sed такое вообще не провернуть — никак не заставить подстановку читать буквально).

Итак, на короткой дистанции (разумеется, у меня был короткий вариант файла для тестов) этот скрипт почти в два раза обогнал предыдущий, но — что было для меня полнейшим разочарованием! — он совершенно заткнулся даже на средней дистанции (нагружая процессор на четверть, но размазано по потокам), то есть я его тупо прервал на какой-то там 10-ой минуте, так что о проверке на полном файле речи уже не шло. Как это понимать — не знаю, поясните. 1000 строк он смолол где-то за секунду с небольшим, а 3000 — уже застрял.

Ладно, пока не было новых идей, прочитал главу про ассоциативные массивы и сразу почуял, что это походу то, что мне надо, но я не ожидал, что настолько! Свойство хеша не дублировать индексы как будто специально создано для решения этой задачки. Быстро стало понятно, что надо просто переложить строки из обыкновенного массива в индексы хеша, а в значения хеша — номера индексов строк из обыкновенного массива.

file=$1
mapfile -t list < <(grep -v '^$' $file)
declare -A hash
for i in ${!list[*]}; do
    hash[${list[i]}]=$i
done
for i in "${!hash[@]}"; do
    final[${hash[$i]}]="$i"
done
printf '%s\n' "${final[@]}" > $file

Это было круто! Нет смысла даже говорить о времени выполнения этой программы, она работает почти мгновенно, менее двух десятых секунды.

Получается на bash таки можно что-то программировать и оно может работать быстро.


UPD

Наконец подсказали, что во втором скрипте у меня ошибка: read -r очищает строку от пробельных символов по краям, поэтому grep её не находит и получается бесконечный цикл. Спасибо @mky: Хочу говорить про Bash (комментарий)
Теперь этот скрипт переваривает тот же файл за 15-16 секунд!

file=$1
list=$(tac $file | grep -v '^$')
while [[ $list ]]; do
    IFS=$'\n' read -r line <<< $list
    list=$(grep -Fxve "$line" <<< $list)
    echo "$line"
done | tac > $file

UPD2

Спасибо анону, подкинул идею отфильтровать хешем в один проход: Хочу говорить про Bash (комментарий)

file=$1
mapfile -t list < <(tac $file | grep -v '^$')
declare -A hash
for i in "${list[@]}"; do
    [[ ${hash[$i]} ]] && continue
    hash[$i]=added
    final+=("$i")
done
printf '%s\n' "${final[@]}" | tac > $file

UPD3

Продолжаю благодарить анона, что замотивировал таки раскурить sort. Итак, самый шустрый вариант:

file=$1
list=$(< $file \
    grep -vn '^$' |
    tac |
    sort -t: -k2 -u |
    sort -t: -k1,1n |
    cut -d: -f2-)
echo "$list" > $file

На моём компе меньше трёх сотых секунды!

★★★★★

Последнее исправление: papin-aziat (всего исправлений: 12)
Ответ на: комментарий от dmitry237

Обязательно! Просто хочу, чтобы файл пока оставался максимально всратым, чтобы на нём выбрать алгоритм-победитель по удалению дубликатов. И то вон, практика показала, что он недостаточно упорот, в нем нет ни одной строки начинающейся с - 😀

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от dmitry237

Кажется, ты не знаешь смысла, зачем надо удалять команды начинающиеся с пробела (а не менять факты в истории). В общем, уже после примера на powershell ценность твоих сообщений резко упала.

anonymous
()
Ответ на: комментарий от anonymous

зачем надо удалять команды начинающиеся с пробела

Это понятно, они не попадают в автодополнение, но вот почему в историю попадают пустые строки мне не понятны.

В общем, уже после примера на powershell ценность твоих сообщений резко упала

Ну я же должен объяснить сам принцип с файлами истории.
pwsh, это и плюсы и минусы, удобный шелл, но слишком дорогие скрипты, при запуске исполняемого скрипта с pwsh-шебангом за собой тянет рантайм, а это ~150мб (bash чуть больше 5мб).

dmitry237 ★★★★
()
Ответ на: комментарий от anonymous

У меня всё работает.

Вот этот тест не показывает двойную подстановку?

$ l=\$foo
$ foo=bar
$ declare -A hash
$ hash=([\$foo]=1)
$ [[ -v hash[$l] ]] && echo true || echo false
false
$ eval echo $l
bar
$ eval echo \$l
$foo
$ [[ -v hash[\$l] ]] && echo true
true

Какая версия баш?

GNU bash, version 4.4.20(1)-release (x86_64-redhat-linux-gnu)

.

[[ ${exists[$l]:+exists} ]] && continue

Да, я покумекал на эту тему, но пользы не увидел. В твой вариант с [[ ведь и так будет подставлено либо строка, либо NULL.

Ради того, чтобы попрактиковать «язык переменных», можно было бы вот так изогнуться

${exists[$l]+fasle} || continue

но это вряд ли потом можно прочитать без боли, да и просто выпендрёжь получается 😀

«Антибаш» …

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

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от papin-aziat

Вы, всё таки, сначала прочитайте bashpitfall, а уже потом ко всяким eval и арифметики с ассоциативными массивами переходите.

А то будет у вас там в истории $( rm -rf ~) и на этом ваш кодинг после выполнения:

(( ${exists[$l]} )) && continue
закончится. Если лень читать весь Pitfall, то пример 62, плюс https://unix.stackexchange.com/questions/627474/how-to-use-associative-arrays...

И, так как поведение bash с арифметикой с ассоциативными массивами меняется от версии к версии, проще просто никогда не сувать ассоциативный массив в двойные круглые скобки, чем помнить все особенности.

mky ★★★★★
()
Ответ на: комментарий от mky

так как поведение bash с арифметикой с ассоциативными массивами меняется от версии к версии, проще просто никогда не сувать ассоциативный массив в двойные круглые скобки, чем помнить все особенности

Хорошо. Просто на этапе изучения я пробую всё, но мне уже (кажется) становится понятно, что если можно работать со строкой, то наверное лучше со строкой даже если это число.

А то будет у вас там в истории $( rm -rf ~) и на этом ваш кодинг после выполнения:

(( ${exists[$l]} )) && continue

закончится.

Я подумал, что в данном случае будет либо 1, либо NULL. Не подумал бы, что эта конструкция может выполнить код, зачем?

Ну, а если пришибёт, то будет мне наука, а актуальной бекапчик системы имеется 😎

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от papin-aziat

Вот этот тест не показывает двойную подстановку?

И не должен, у анона 5.2. Одна из отличий 5.2 от 5.1:

Bash-5.2 attempts to prevent double-expansion of array subscripts under certain circumstances, especially arithmetic evaluation, by acting as if the `assoc_expand_once' shell option were set.

А assoc_expand_once (это shell options) появилась в 5.0, ЕМНИП, и она может быть включена в начале скрипта, для явного запрета двойной подстановки.

Вот вы и добрались до разного поведения баш-скриптов и разборов, почему у одного «УМВР», а у другого скрипт творит непонятно что.

mky ★★★★★
()
Ответ на: комментарий от mky

Ну ты голова! У меня, к сожалению, такой опции нет, надо подумать о новом баше

$ shopt | grep assoc || echo облом
облом

😧

papin-aziat ★★★★★
() автор топика
Последнее исправление: papin-aziat (всего исправлений: 1)
Ответ на: комментарий от mky

https://unix.stackexchange.com/questions/627474/how-to-use-associative-arrays

Спасибо за ссылку, оставлю и погоняю примеры на своём bash-4.4, так как это системный шелл, то надо знать о проблемах.

Таким образом получается, что башистам надо уметь писать переносимый код между версиями баша, — жесть! Стартовый пост подправил.

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

$ cat test
aaa

bbb
$ mapfile -t list < test
$ declare -A hash
$ for i in "${list[@]}"; do hash[$i]=1; done
bash: hash[$i]: bad array subscript
$ declare -p hash
declare -A hash=([aaa]="1" )

😱

Кстати, я узнал об ассоциативных массивах из книги «Идиомы bash» (Карл Олбинг, Джей Пи Фоссен) и там на странице 106 под номером 7 дана такая идиома в коде подсчёта слов:

(( myhash[$line]++ ))

🙂

В книге о баше-5 говорят как о будущем.

papin-aziat ★★★★★
() автор топика
Последнее исправление: papin-aziat (всего исправлений: 2)
Ответ на: комментарий от papin-aziat

С какой-то верии 5.0, 5.1 может быть и пустой индекс ассоциативного массива и из пробелов. Причём, ЕМНИП, в какой-то момент была баго-фича, что независимо от числа пробелов строка считалась за индекс из одного пробела, а потом поправили — один и два пробела — разные индексы.

переносимый код между версиями баша, — жесть!

Жесть заключается в том, что никто не знает, что поменяют. То есть в любой момент старый скрип, проработавший N+1 лет, начинает работать как-то не так, а если он большой-развесистый, то можно долго понимать, что его сломало. Например, ещё не проверял, но написано, что в 5.2 конструкция с бэкслешем ${hash[\$i]} не работает.

Конечно, можно в начале скрипта проверять $BASH_VERSION и перестовать работать, если версия неизвестная. Но на ролинг дистрах не вариант.

Может, когда-нибудь, ChatGPT или что подобное научится проверять скрипт на совместимость версий. Не всякий бред генерить с нуля, а ошибки искать в имеющимся коде. Типа дал ему скрипт, написал, что скрипт правильно работал на версии 2.05, и пусть ИИ напишет, что сломается на версии 5.2. Этим ведь не только bash страдает, куча относительно полезных скриптов на питоне 2.7.

mky ★★★★★
()
Ответ на: комментарий от anonymous

Твой не проверял, просто сделал по аналогии, — работает!

file=$1
list=$(< $file \
    grep -vn '^$' |
    tac |
    sort -t: -k2 -u |
    sort -t: -k1,1n |
    cut -d: -f2-)
echo "$list" > $file

Сравнивал результат с «эталонным» файлом, обработанным скриптом с массивами, — полное совпадение. Разумеется, это самый быстрый вариант: у меня меньше трех сотых секунды.

Другие варианты (читый баш, awk) уходят в своп, если повезет выбивает oom-киллер

Вот этого я не понял, что там и куда успевает уходить за это время 🙂

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от mky

Жесть заключается в том, что никто не знает, что поменяют. То есть в любой момент старый скрип, проработавший N+1 лет, начинает работать как-то не так, а если он большой-развесистый, то можно долго понимать, что его сломало.

Этим ведь не только bash страдает, куча относительно полезных скриптов на питоне 2.7.

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

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от papin-aziat

Вроде экономить тут не на чем, там даже не мегабайты.

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

CrX ★★★★★
()
Ответ на: комментарий от CrX

Приходится помнить не только об экранировании, но и о формате, ведь команда не поймёт, где там именно строка, а где ключи, поэтому если оно с дефиса, например начинается, могут быть проблемы.

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

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от papin-aziat
file=$1
list=$(< $file \
    grep -vn '^$' |
    tac |
    sort -t: -k2 -u |
    sort -t: -k1,1n |
    cut -d: -f2-)
echo "$list" > $file

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

import sys
from typing import Iterator


def _filer_uniq(file_name: str) -> Iterator[str]:
    with open(file_name) as f:
        lines = f.readlines()
        uniq = set(lines)
    for l in reversed(lines):
        if l in uniq:
            yield l
            uniq.remove(l)
            
if __name__ == '__main__':
    for l in reversed(list(_filer_uniq(sys.argv[1]))):
        print(l, end='')

в последнем понятна абсолютно любая строка - что она делает и зачем, и скрипт в целом не вызывает вопросов. А вот что делает sort -t: -k1,1n надо лезть в мануал, я не готов сразу ответить, что за магия -k1,1n

FishHook
()
Ответ на: комментарий от FishHook

Да, выглядит ужасно, и наверное лучше было бы оставить это в виде одной строки, а не пытаться изобразить какой-то порядок, а то увидит такое человек и подумает, что этот язык программирования некрасивый и я его учить не бубу 😁

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от anonymous

вы правы, в третьем питоне словарь гарантирует сохранение порядка элементов, можно было бы обойтись без множества. Если бы не было условия сохранять более свежие сроки истории, то есть исходный список надо отреверсить и тогда словать не очень помогает.

FishHook
()
Ответ на: комментарий от FishHook

исходный список надо отреверсить и тогда словать не очень помогает

Я привел функцию uniq c сохранением порядка. Твой пример слишком «обфусцированный». Намного легче воспринимается последовательность (композиция функций) reverse(uniq(reverse(lines))

anonymous
()
Ответ на: комментарий от mky

Например, ещё не проверял, но написано, что в 5.2 конструкция с бэкслешем ${hash[$i]} не работает.

Настроил песочницу с 5.2, просто собрал из тарболла с сайта GNU, но никаких патчей не накладывал (не умею).

Рабочего кода именно с такой подстановкой у меня нет, зато протестил этот вариант:

file=$1
mapfile -t list < <(tac $file | grep -v '^$')
declare -A hash
for i in "${list[@]}"; do
    [[ -v hash[\$i] ]] && continue
    hash[$i]=added
    final+=("$i")
done
printf '%s\n' "${final[@]}" | tac > $file

Не работает! Просто молча перегнал все строки (кроме пустых).

Кстати, вот так по умолчанию:

$ shopt | grep ^assoc
assoc_expand_once	off

Так и оставил.

Далее, убрал экранирование:

[[ -v hash[$i] ]] && continue

Работает! Всё чётко, хотя и с отключенным assoc_expand_once… Теоретически, не должно было ведь работать, да? Или дело не в двойной подстановке?

Конечно, можно в начале скрипта проверять $BASH_VERSION и перестовать работать, если версия неизвестная. Но на ролинг дистрах не вариант.

Вернул экранирование:

[[ -v hash[\$i] ]] && continue

Добавил в начало скрипта:

shopt -s compat44

Работает!

Получается можно до посинения сидеть на 4.4, а потом в рабочие скрипты добавить совместимость. Было бы круто, если бы это вот так просто работало.

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от mky

А то будет у вас там в истории $( rm -rf ~) и на этом ваш кодинг после выполнения:

(( ${exists[$l]} )) && continue

закончится.

Надо закрыть по этому вопросу. Здесь нет проблем, видимо в конструкцию ${} оболочка не лезет и подстановкой занимается только [].

bash-4.4

$ > foo
$ line='$(rm foo)'
$ declare -A assoc
$ (( ${assoc[$line]} )) || assoc[$line]=1
$ echo ${assoc[$line]}
1
$ ls foo
foo
$ [[ ${assoc[$line]} ]] && assoc[$line]=2
$ echo ${assoc[$line]}
2
$ ls foo
foo

А вот это да:

$ [[ -v assoc[$line] ]]
bash: assoc: bad array subscript
$ ls foo
ls: cannot access 'foo': No such file or directory
$ > foo
$ (( assoc[$line]++ ))
bash: assoc: bad array subscript
rm: cannot remove 'foo': No such file or directory
bash: assoc[$(rm foo)]: bad array subscript

Инкремент совсем какой-то жёсткий вариант, там получается даже два раза rm отрабатывает что ли?

Ладно, в 5.2 это починили, а вот чтобы такую переменную убить, в обоих версиях (для надёжности) надо ломать не только [], которая может оказаться диапазоном символов

unset assoc[$line\]

но и подстановку переменной, так как она может содержать пробелы и получаются несуществующие варианты переменных массива

unset assoc[\$line\]

Причем в 4.4 ругается

$ unset assoc[$line\]
bash: unset: `assoc[$(rm': not a valid identifier
bash: unset: `foo)]': not a valid identifier

а в 5.2 просто тихо ничего не делает, почему-то, наверное настройки какой-нибудь не хватает.

papin-aziat ★★★★★
() автор топика
Ответ на: комментарий от papin-aziat

Инкремент, вроде, два раза выполняет индекс массива до 5.0 или 5.1, там уже один раз. Но это не точно.

Да, ${} спасает, но ассоциативные массивы в арифметических выражениях — минное поле. С одной стороны, могут что-то поменять в новых версиях, а с другой -- со временем возникает привычка писать в арифметических выражениях массивы без фигурных скобок. Так как ${} требуют после себя пробел и нельзя написать (( ${assoc[$line]}++ )).

У unset, попробуйте ещё одиночные кавычки unset 'assoc[$line]'. Они же должны помочь и с [[ -v .

Что касается "shopt -s compat44", то сейчас положено устанавливать переменную среды BASH_COMPAT=4.4, а не опцию. И я не знаю, куда это приведёт, ведь в исходниках bash возникает куча условных блоков. Да, плюс, что должен делать скрипт, если я в начале скрипат в 5.2 создал ассоциативный массив с индексами из пробелов, а потом объявил, что у меня 4.4?

Я не пользовался этой возможностью, ИМХО, там можно словить, что 5.2 с BASH_COMPAT=4.4 в каких-то нюансах будет себя вести и ни как 4.4 и ни как 5.2, а как-то ещё по-другому. И во это отловить будет совсем нереально.

mky ★★★★★
()
Ответ на: комментарий от mky

а с другой – со временем возникает привычка писать в арифметических выражениях массивы без фигурных скобок. Так как ${} требуют после себя пробел и нельзя написать (( ${assoc[$line]}++ ))

Я не понимаю, ты зачем инкрементируешь подстановку? Я такого выражения пока ещё нигде не встречал. Если надо подставить значение, то ведь: $(( assoc[$line]++ )), и тут в 4.4, очевидно, надо экранировать.

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

$ foo=1
$ echo $(( foo++ ))
1
$ echo $foo
2

Это не то, что я интуитивно ожидаю. Я почему-то ожидаю, вот такое:

$ foo=1
$ echo $(( foo = foo + 1 ))
2
$ foo=1
$ echo $(( foo += 1 ))
2

А такое даёт именно префиксный вариант:

$ foo=1
$ echo $(( ++foo ))
2

У unset, попробуйте ещё одиночные кавычки unset 'assoc[$line]'. Они же должны помочь и с [[ -v .

Да, разумеется, просто я нахожусь на этапе разбора мелочей и поэтому стараюсь экранировать прицельно, чтобы осознавать, что именно я экранирую, а иначе рабочий вариант тотального экранирования типа 'assoc[$line]' или assoc'[$line]' скроет от новичка нюансы работы языка.

Что касается «shopt -s compat44», то сейчас положено устанавливать переменную среды BASH_COMPAT=4.4, а не опцию. И я не знаю, куда это приведёт, ведь в исходниках bash возникает куча условных блоков.

Я б так рассудил. Если скрипт не может сделать ничего опасного, то попробовать совместимость, а если прям вот боевой скрипт и сразу в рабочей обстановке, то лучше уж на старой версии баша (от греха подальше) 🙂

Да, плюс, что должен делать скрипт, если я в начале скрипат в 5.2 создал ассоциативный массив с индексами из пробелов, а потом объявил, что у меня 4.4?

Я не пользовался этой возможностью, ИМХО, там можно словить, что 5.2 с BASH_COMPAT=4.4 в каких-то нюансах будет себя вести и ни как 4.4 и ни как 5.2, а как-то ещё по-другому. И во это отловить будет совсем нереально.

Не могу себе представить, зачем может понадобиться такое. Если написал на новом баше и надо перетащить на старую систему, а тонкости забыл? Наверное надёжнее будет перетащить такой скрипт вместе с новым башем. Благо, сборка баша занимает секунды, требуется только gcc, а make install DESTDIR=/path/to/root-dir сложит всё в одно место:

If you want to see the files bash will install and where it will install them without changing anything on your system, specify the variable 'DESTDIR' as an argument to 'make'.  Its value should be the absolute directory path you'd like to use as the root of your sample installation tree.

Удобно!

papin-aziat ★★★★★
() автор топика
Последнее исправление: papin-aziat (всего исправлений: 1)
Ответ на: комментарий от mky

Ага, спасибо за помощь и информацию, удачи!

Я ещё там одну темку хочу докурить из этого треда, там тоже твой пост, ответа ждать не буду 🙋‍♂️

papin-aziat ★★★★★
() автор топика