LINUX.ORG.RU

bash. Упороться по функциям: туда и обратно.

 , ,


0

1

Hi, All!

Светит мне разработка некоторой системы скриптов на баше, на пару тысяч строк. Не надо сразу писать, что такое не стоит писать на баше — это условие задачи, и ставил его не я.
И решил я по такому поводу попробовать широко распространенный совет: «everything is function». А если придерживаться другой популярной концепции, «everything is local», и третьей («arrays wherever possible»), то тут возникает вопрос о передаче параметров внутрь функции, и о добыче результатов из нее.
Насколько мне известно, баш не обладает возможностями передать массив как массив — штатная передача внутрь как "${array[*]}" разворачивает массив в строку, что обесценивает оригинальную идею.
Можно передавать внутрь ссылку на массив. Но, во-первых, это только с v4.3 работает. Во-вторых, вот способ, который мне понравился больше:

#!/bin/bash

set -o nounset
set -o errexit
set -o pipefail

f1() {
    local var1="${f1_data[var1]}"
    local ttt

    local i=0
    while [[ "${f1_data[var2.$i]:-}" ]]; do
        ttt="${ttt:-} ${f1_data[var2.$i]}"
        let i=$i+1
    done

    ttt="${ttt:-} --- ${var1}"
    eval "$1='${ttt}'"

    unset f1_data
}

main() {
    local -A f1_data=(
        [var1]="v1"
        [var2.0]="bebe"
        [var2.1]="oo"
        [var2.2]="rwx"
    )
    local result_var="some default"

    f1 result_var \
        || echo "ALARM!" >&2

    echo "And the result is \"${result_var}\"!"
}

main

Т.е. непосредственно перед вызовом функции все, что ей надо, засовывается в массив с именем [function_name]_data, который обнуляется в конце работы функции. Я использовал ассоциативный массив, но это просто мне так больше нравится.

Да, и про передачу значения наружу тоже в этом же примере.
Использование eval позволяет избавиться от необходимости передавать значение наружу через echo что, в свою очередь, позволяет изящнее организовать обработку ошибок вызова функций.

Ваше мнение?

Буду благодарен за критику и советы.


это условие задачи, и ставил его не я

Тебя подставили!

anonymous
()

такое не стоит писать на баше — это условие задачи, и ставил его не я.

Если упарываться то правильно = https://github.com/eklitzke/c.sh

Deleted
()

У тебя идиотический подход. Это критика. А совет, забей на баш, есть питон, пёрл и повершелл.

anonymous
()

Это ловушка. Беги.

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

У тебя идиотический подход. Это критика.

Спасибо.

Не могли бы вы разъяснить, в чем именно состоит идиотичность подхода?

Borifed
() автор топика

пару тысяч строк

пфффф

$ wc -l sanity.sh
   20763 sanity.sh

Ваше мнение?

Как говорил ребе, что в рубашке, что без рубашки - кончится все одним и тем же. На баше тебя ждут боль и страдание независимо от.

anonymous
()

Можно передавать внутрь ссылку на массив

Использование eval позволяет избавиться от необходимости передавать значение наружу

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

Про саму идею в целом. Зачем тащишь неродные идеи в баш, потом героически с этими идеями борешься? Будь проще.

anonymous
()

Можно передавать внутрь ссылку на массив. Но, во-первых, это только с v4.3 работает.

Можно передавать имя массива и копировать в local.

Т.е. непосредственно перед вызовом функции все, что ей надо, засовывается в массив с именем [function_name]_data

Это неудобно, если вам надо вызвать две разные функции с одинаковыми данными, пусть и с небольшим изменением. И вообще, если у вас имя входного параметра жестко задано по имена функции, то зачем эти все извращения? Оно же у вас глобальное в контексте вызова.

eval «$1='${ttt}'»

Это сломается, если в ttt будет одинарная кавычка. Или $1 есть ttt. Рекомендуется юзать _ttt и eval $1=\$_ttt

local i=0
let i=$i+1

Устарело давно let.

local -i i

i+=1

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от anonymous

Зачем тащишь неродные идеи в баш, потом героически с этими идеями борешься?

Так если родных не хватает, как быть?

Будь проще.

Я бы с радостью. Но как именно?

Borifed
() автор топика

скриптов на баше, на пару тысяч строк

ЬЕГNТЕ

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

Так если родных не хватает, как быть?

Есть такой стереотип про еврев - вопросом на вопрос.

Тебе надо с идеями бороться или задачу решать?

Идеи должны решать возникающие проблемы. Ты сперва упорись в проблему, потом решай их с использованием идей.

Хотя, если ты идейный и проблема - это как бы применить идею, то ...

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

Не могли бы вы разъяснить

Можно и батники писать, но есть же инструменты гораздо лучше, причем прямо из коробки. Шелл скрипты хороши для короткой проверки, есть файл или нет, есть приложение или нет, какая определенна переменная. Но и только. Когда не хватает функционала ты вызываешь sed, awk, grep, другие приложения, но это нагромождение костылей — воскресная ярмарка в Костроме. Где же ваше чувство вкуса? Или самоцель? Ну видали мы разных чудаков, которые не для, а чтобы.

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

У тебя простая задача — написать скрипт на баше. Просто сделай и не занимайся хернёй, пиши в идиоматическом для баша стиле.

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

WitcherGeralt ★★
()

и третьей («arrays wherever possible»),

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

Баш заточен на работу с файлами при помощи программ, объединяемых в конвейеры пайпов. Так и надо на нём писать.

legolegs ★★★★★
()

Использование eval позволяет избавиться от необходимости передавать значение наружу через echo что, в свою очередь, позволяет изящнее организовать обработку ошибок вызова функций.

Лучше через локальную переменную. Да, они протекают внутрь вызываемой функции.

@@ -18 +18 @@
-    eval "$1='${ttt}'"
+    f1_result=${ttt}
@@ -30 +30 @@
-    local result_var="some default"
+    local f1_result="some default"
@@ -35 +35 @@
-    echo "And the result is \"${result_var}\"!"
+    echo "And the result is \"${f1_result}\"!"

Ещё не понятно зачем unset f1_data.

NeXTSTEP ★★
()
    local -A f1_data=(
        [var1]="v1"
        [var2.0]="bebe"
        [var2.1]="oo"
        [var2.2]="rwx"
    )

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

local f1_var1=v1
local -a f1_var2=(bebe oo rwx)
NeXTSTEP ★★
()

штатная передача внутрь как «${array[*]}» разворачивает массив в строку

Передавай как ${array[@]}.

Советую почитать Advanced Bash-Scripting Guide.

NeXTSTEP ★★
()

баш не обладает ... и про передачу значения наружу

Ты забыл ещё один «everything is file». Если твои функции принимают/отдают больше одного вотевера – гоняй их палкой(f1|f2|…).

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

если у вас имя входного параметра жестко задано по имена функции, то зачем эти все извращения? Оно же у вас глобальное в контексте вызова.

Да. Это меня смущает. Это одна из причин появления этого треда.

Но как передать имя массива внутрь функции «и копировать его в local» — я не придумал :о( Подскажете?

Borifed
() автор топика
Ответ на: комментарий от vodz

Рекомендуется юзать _ttt и eval $1=\$_ttt

Да, встречал такой совет. Верно ли я понимаю, что тут расчет основывается на том, что на вход не будет подана переменная с именем _ttt?

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

Ты сперва упорись в проблему, потом решай их с использованием идей.

Проблема тут — читаемость и поддерживаемость простыни на баше.

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

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

Верно ли я понимаю, что тут расчет основывается на том, что на вход не будет подана переменная с именем _ttt?

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

Но как передать имя массива внутрь функции «и копировать его в local» — я не придумал :о( Подскажете?

С ассоциативными массивами не так удобно, так как вам придётся получить ключи, а потом вручную копировать значения. Но обычные массивы легко копируются.

f() {
  local -a '_copy=("${'$1'[@]}")'
}

declare -a A=(1 2 42 0)
f A

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

Можно подробнее?

ну, как-то так:

main() {
  f1 |
  f2
}

Твой пример перепиcать не получается - остаётся одна строковая константа и tr :)

DonkeyHot ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.