Создание переменной по имени (shell)

Модератор: Модераторы разделов

Аватара пользователя
LXj
Сообщения: 94

Создание переменной по имени

Сообщение LXj »

Есть у меня переменная $LINE, ну, к примеру, содержащая строку "extract='unzip'" или "extract='tar -xjf'"
Чтобы мне такого сделать с этой переменной, чтобы появилась новая переменная $extract, содержащая соответственно то значение, которое в $LINE справа от присвоения?

То есть такой эффект я ожидал от `$LINE`, но оно не помогло
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

Код: Выделить всё

export $LINE
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

Хм, что же я такого делал, что у меня с export никакой вариант не работал? Кажется, где-то у меня была опечатка :unsure:
В общем, правильный ответ:

Код: Выделить всё

export "$LINE"

Спасибо за помощь!
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

Ага.
Не всё так просто :huh:
Вот как это происходит в консоли:

Код: Выделить всё

lx@LX ~/bin $ echo "$LINE"
extract='tar -xjf'
lx@LX ~/bin $ export $LINE
bash: export: `-xjf'': not a valid identifier
lx@LX ~/bin $ export "$LINE"
lx@LX ~/bin $ echo "$extract"
'tar -xjf'


Поэтому я написал, что правильнее export "$LINE". Проблема в том, что в скрипте это не работает :(

Вот он скрипт:

Код: Выделить всё

#!/bin/sh

## BAH: Beautiful archiving helper
## Generated by LXj, do not modify;)

SIMPLE_ACTIONS="extract list"

FORMATS="

.tar.gz
.tar.Z
.tgz
extract='tar -xzf'
list='tar -lzf'

.tar.bz2
.tbz2
extract='tar -xjf'
list='tar -ljf'

.zip
extract='unzip'
list='unzip -l'
"

ALL_EXTENSIONS=""
EXTENSION=""

echo "$FORMATS" | while read LINE
do
    ACTION=`echo $LINE | grep "="`
    if [ -z "$LINE" ]
    then
        if [ -n "$EXTENSION" ]
        then
            break
        fi
    elif [ -z "$ACTION" ]
    then
        ALL_EXTENSIONS="$ALL_EXTENSIONS $LINE"
        EXTENSION=`echo $2 | grep "$LINE"`
    elif [ -n "$EXTENSION" ]
    then
        export "$LINE" # без кавычек будет такая же ошибка, как в примере вверху
    fi
done

echo $extract
echo $list


Запускаю:

Код: Выделить всё

lx@LX ~/opt/writer2latex04 $ bah list w2lfilters.zip


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

PS. На самом деле скрипт должен заканчиваться примерно так:

Код: Выделить всё

`eval "\$$1"` $2

... но должны быть ещё проверка ошибок и другие фичи ;)

Update Подгулявшее форматирование исправил :)

Странная вещь...
ALL_EXTENSIONS должна в конце цикла (если выходим не по break) содержать все возможные расширения (для вывода хелпа)
Если в цикл добавить вывод значений ALL_EXTENSIONS, то мы видим, как эти расширения дописываются в переменную
Если же вывод поставить ПОСЛЕ цикла, то переменная также окажется пустой!
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

Вы, батенька, извращенец.

Код: Выделить всё

#!/bin/bash
FORMATS=(
        "\.tar\.gz \.tar\.Z \.tgz"
        "\.tar\.bz2 \.tbz2"
)
EXTRACT=(
        "tar -xzf"
        "tar -xjf"
)
LIST=(
        "tar -lzf"
        "tar -ljf"
)

FILETYPE=""
i=0
while [[ $i -lt ${#FORMATS[@]} && -z $FILETYPE ]]; do
        for filetype in ${FORMATS[$i]}; do
                [ `expr match "$2" ".*${filetype}\$"` -ne 0 ] && FILETYPE=$i
        done
        let i+=1
done

if [ -z $FILETYPE ]; then
        echo "HELP!!!"
        exit;
fi

if [ "$1" == "extract" ]; then
        echo ${EXTRACT[$FILETYPE]} "$2"
elif [ "$1" == "list" ]; then
        echo ${LIST[$FILETYPE]} "$2"
else
        echo 'bad argument'
fi


Дарю.

При небольших изменениях должен и не под bash работать... правда, не помню, что именно реализует array.
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

IFL писал(а):
11.06.2006 23:35
Вы, батенька, извращенец.
Я просто ничего серьёзного в шелле не писал, а тут появилась идейка, которую решил с помощью шелла-таки и реализовать. Вот, скажем, у меня такое ощущение, что массивы в sh я вижу в первый раз. :blink: Это во-первых. Ну, не читал я раньше доков по шеллам, а тут взял первую попавшуюся и очень старую. Так что там ни массивов, ни ==, ни += там не было.

Во-вторых, главная цель, которую я преследовал, когда придумывал формат "конфигурационной части" -- это расширяемость. Мой черновой вариант на самом деле во многом похож на предложенный вами код

Код: Выделить всё

## Detecting file format...

if   [ `echo $2 | grep ".tar.gz$"` ]
then
    export EXTRACT="tar -xzf"
    export LIST="tar -lzf"
elif [ `echo $2 | grep ".tar.bz2$"` ]
then
    export EXTRACT="tar -xjf"
    export LIST="tar -ljf"
elif [ ` echo $2 | grep ".zip$" ` ]
then
    export EXTRACT="unzip"
    export LIST="unzip -l"
else
    echo "Invoke bah as"
    echo "    bah action file"
    echo "where action could be list or ex[tract]"
    echo "and file must be .tar.gz, .tar.bz2, .zip"
fi

## Running archiver

if   [ `echo $1 | grep "list"` ]
then
    $LIST $2
elif [ `echo $1 | grep "ex"` ]
then
    $EXTRACT $2
fi


(это было написано до того, как я вообще читал какие-либо доки по шеллу, по-этому всё так плохо :D )

Так вот, представьте, что ваш или мой старый вариант скрипта потребуется расширить (я ведь не для того его пишу, чтобы предоставить интерфейс к двум функциям трех программ, вы ведь понимаете?). Скажем, добавить поддержку ещё десятка архиваторов. Или ещё десятка различных действий. Придётся перекапывать весь код! В моём втором варианте нужное место локализуется очень просто. Добавление нового архиватора требует изменения всего в одном месте. Добавление нового действия немного сложнее, но по крайней мере не требует дополнительного if-а в другом конце скрипта. В идеале в один файл выносится $FORMATS, в другой -- определение сложных действий, а скрипт сокращается до десятка-двух строчек (пока опциями не обрастет, конечно :) Но зато обработка опций не будет прилеплена намертво к конфигурации подключаемых архиваторов)

Да, может быть планы для шелла слишком наполеоновские и надо было писать на питоне/перле. Но зато этот скрипт заставил-таки меня нормально прочитать (и кое-что запомнить) доки к шеллу.

В общем, вопрос остается в силе

Update С точки зрения читабельности гораздо удобнее иметь всю информацию об одном формате архивов в одном месте, группировка же по действиям более удобна для реализации но для пользователя значения абсолютно не имеет
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

Код: Выделить всё

A="extract='tar -blah'"
eval "$A"
echo $extract

Прекрасно работает.

Так что вам не нравится в моём коде?
Вы не поняли как добавить формат?
Элементарно. Добавляем строку в FORMATS и массивы действий.
С добавлением действий, как и в вашем, надо модифицировать конец скрипта.
Если уж так хочется всё динамическое:

Код: Выделить всё

#!/bin/bash
FORMATS=(
    "\.tar\.gz \.tar\.Z \.tgz"
    "\.tar\.bz2 \.tbz2"
)
ACT_EXTRACT=(
    "tar -xzf"
    "tar -xjf"
)
ACT_LIST=(
    "tar -lzf"
    "tar -ljf"
)

ACTS=(
    "LIST"
    "EXTRACT"
)

if [[ -z $1 || -z $2 ]]; then
    echo 'help'
    exit;
fi

FILETYPE=""

i=0
while [[ $i -lt ${#FORMATS[@]} && -z $FILETYPE ]]; do
    for filetype in ${FORMATS[$i]}; do
        [ `expr match "$2" ".\+${filetype}\$"` -ne 0 ] && FILETYPE=$i
    done
    let i+=1
done

if [[ -z $FILETYPE || " ${ACTS} " !=  *" $1 "* ]]; then
    echo "bad filetype or bad action"
    exit;
fi

TMP="\${ACT_$1[$FILETYPE]}"
ACTION=`eval echo $TMP`
echo $ACTION "$2"

Про форматы -- не изменилось
Про действие -- добавляем в массив ACTIONS действие, и пишем для него

Код: Выделить всё

ACT_<новое действие>=(
    "<команда для первой группы архивов>"
    "<команда для второй группы архивов>"
...
)
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

На самом деле, если бы мне было нужно, я бы сделал:

Код: Выделить всё

#!/bin/bash
FORMATS=(
        ".\+\.tar\.gz\$ .\+\.tar\.Z$ .\+\.tgz\$"
        ".\+\.tar\.bz2\$ .\+\.tbz2\$"
)
ACT_EXTRACT=(
        "tar -xzf \$2"
        "tar -xjf \$2"
)
ACT_LIST=(
        "tar -lzf \$2"
        "tar -ljf \$2"
)

ACTS=(
        "LIST"
        "EXTRACT"
)

if [[ -z $1 || -z $2 ]]; then
        echo 'help'
        exit;
fi

FILETYPE=""

i=0
while [[ $i -lt ${#FORMATS[@]} && -z $FILETYPE ]]; do
        for filetype in ${FORMATS[$i]}; do
                [ `expr match "$2" "${filetype}"` -ne 0 ] && FILETYPE=$i
        done
        let i+=1
done

if [[ -z $FILETYPE || " ${ACTS} " !=  *" $1 "* ]]; then
        echo "bad filetype or bad action"
        exit;
fi

TMP="\${ACT_$1[$FILETYPE]}"
TMP=`eval echo $TMP`
eval $TMP
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

Большое спасибо!

Нет, я-то понял, как их добавлять. Проблема в том, что ТАК их добавлять неудобно (что я подробно расписал в своём предыдущем сообщении). Конечно, это моё имхо. Но не стану же я писать скрипт, которым лично мне не удобно пользоваться?
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

LXj писал(а):
13.06.2006 15:55
Большое спасибо!

Нет, я-то понял, как их добавлять. Проблема в том, что ТАК их добавлять неудобно (что я подробно расписал в своём предыдущем сообщении). Конечно, это моё имхо. Но не стану же я писать скрипт, которым лично мне не удобно пользоваться?

Давай построчно.
Так вот, представьте, что ваш или мой старый вариант скрипта потребуется расширить (я ведь не для того его пишу, чтобы предоставить интерфейс к двум функциям трех программ, вы ведь понимаете?). Скажем, добавить поддержку ещё десятка архиваторов. Или ещё десятка различных действий. Придётся перекапывать весь код!

"Весь" код -- это начало скрипта

В моём втором варианте нужное место локализуется очень просто.

А в моём как будто сложно?
Добавление нового архиватора требует изменения всего в одном месте.

Ну, а у меня 1 + количество действий, притом, что всё находится рядом.
Добавление нового действия немного сложнее, но по крайней мере не требует дополнительного if-а в другом конце скрипта.

Мой второй вариант имеет динамическое определение действий, если ещё дописать совсем немного, то можно сделать поддержку алиасов...

В идеале в один файл выносится $FORMATS, в другой -- определение сложных действий, а скрипт сокращается до десятка-двух строчек (пока опциями не обрастет, конечно smile.gif Но зато обработка опций не будет прилеплена намертво к конфигурации подключаемых архиваторов)

На самом деле, в bash'е не особо принято разделять на файлы...
Это всё-таки больше самостоятельные скрипты...

Лирическое отступление: вообще, у вас в 1 переменной получается одна большая помойка...
Вообще, по хорошему, надо решать задачу многомерными массивами... которых у нас нет, либо использовать несколько одномерных...
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

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

Мне кажется, что удобней всё-таки, если информация сгруппирована по форматам. У вас же она сгруппирована в первую очередь по действиям.


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

UPD: Кажется, я понял. Цикл получает данные из фильтра, а потому выполняется в отдельном шелле. То есть так, как если бы я написал

Код: Выделить всё

echo "$FORMATS" | (while read LINE; ... done)
При выходе из этого шелла все переменные, конечно, и сбрасываются. Ну а поскольку

Код: Выделить всё

for LINE in $FORMATS
не будет дружить с пробелами, мне точно нужно смотреть в сторону массивов
Спасибо сказали:
Аватара пользователя
madskull
Сообщения: 1019
Статус: Экс-металлюга

Re: Создание переменной по имени

Сообщение madskull »

(LXj @ Jun 15 2006, в 01:34) писал(а):UPD: Кажется, я понял. Цикл получает данные из фильтра, а потому выполняется в отдельном шелле. То есть так, как если бы я написал
Код
echo "$FORMATS" | (while read LINE; ... done)
При выходе из этого шелла все переменные, конечно, и сбрасываются. Ну а поскольку
Код
for LINE in $FORMATS
не будет дружить с пробелами, мне точно нужно смотреть в сторону массивов


LXj
Решений для твоей проблемы с пробелами и отдельным шеллом как минимум два:

Код: Выделить всё

while read LINE; ... done < <(echo "$FORMATS")


и

Код: Выделить всё

IFS=$'\n'; for LINE in $FORMATS;do  .... done



PS. Прошу прощения, особо в весь топик не вникал и ответил только на отквоченное.
ArchLinux / IceWM
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

madskull писал(а):
15.06.2006 08:47
Решений для твоей проблемы с пробелами и отдельным шеллом как минимум два:
Спасибо! Я решил всё-таки сделать через массив... Массив получился похож на двумерный :D

Код: Выделить всё

ACTS=(
        "list"
        "extract"
)

FORMATS=(
    "PATTERNS='.\+\.tar\.gz\$ .\+\.tar\.Z$ .\+\.tgz\$';
     extract='tar -xzf $2';
     list='tar -tzf $2';
    "

    "PATTERNS='.\+\.tar\.bz2$ .\+\.tbz2$';
     extract='tar -xjf $2';
     list='tar -tjf $2';
    "

    "PATTERNS='.\+\.zip$';
     extract='unzip $2';
     list='unzip -l $2';
    "
)

ALL_PATTERNS=""
FOUND=""
FILETYPE=""

i=0
while [[ $i -lt ${#FORMATS[@]} && -z $FILETYPE ]]; do
    eval "${FORMATS[$i]}"
    for filetype in $PATTERNS; do
    [ `expr match "$2" "${filetype}"` -ne 0 ] && FILETYPE=$i
    done
    ALL_PATTERNS="$ALL_PATTERNS $PATTERNS"
    let i+=1
done

if [[ -z $FILETYPE || " ${ACTS} " !=  *" $1 "* ]]; then
        echo "BAH!"
    echo "Usage:"
    echo "    bah <action> <filename> [<path>]"
    echo "Where <action> is one of"
    echo "list - list files in archive"
    echo "extract - extract all files to current directory"
    echo "BAH supports following archive types:"
    echo $ALL_PATTERNS
        exit;
fi

TMP=\$$1
eval $TMP


Правда, проверка " ${ACTS} " != *" $1 "* некорректна. В общем, я сейчас хочу спать, допишу в другой раз :)

UPD: Да, предвидя вопросы об использованни $2 в FORMATS...
Одно из действий будет выглядеть так:
* Для tar-ов
$find = $list
* для zip-ов
$find = "unzip -l $2 | head -n-3 | tac | head -n-3 | tac | colrm 1 28 "

Отличие от find от list -- унифицированный формат для вывода. Ещё будет ls -- он будет выводить файлы только одной директории, по-этому в хелпе упоминается <path>
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

LXj писал(а):
15.06.2006 09:49
Правда, проверка " ${ACTS} " != *" $1 "* некорректна. В общем, я сейчас хочу спать, допишу в другой раз :)

В чём её некорректность?
Берётся и смотрится если элемент в массиве...
Конечно же, учитывается регистр...

Получилось некое перепевание скрипта, притом, что в некоторых местах можно было бы и упростить/сделать просто по-другому...
Хотя нет, всё таки появилась нечто никому ненужное:

Код: Выделить всё

FOUND=""
Спасибо сказали:
Аватара пользователя
LXj
Сообщения: 94

Re: Создание переменной по имени

Сообщение LXj »

IFL писал(а):
15.06.2006 10:20
LXj писал(а):
15.06.2006 09:49

Правда, проверка " ${ACTS} " != *" $1 "* некорректна. В общем, я сейчас хочу спать, допишу в другой раз :)

В чём её некорректность?
У меня она почему-то не находит элементы массива, кроме list

IFL писал(а):
15.06.2006 10:20
Получилось некое перепевание скрипта, притом, что в некоторых местах можно было бы и упростить/сделать просто по-другому...

Это ещё не конец :)

IFL писал(а):
15.06.2006 10:20
Хотя нет, всё таки появилась нечто никому ненужное:

Код: Выделить всё

FOUND=""
Не вычистилось из предыдущей версии
Спасибо сказали:
Аватара пользователя
KiWi
Бывший модератор
Сообщения: 2521
Статус: статус, статус, статус

Re: Создание переменной по имени

Сообщение KiWi »

LXj писал(а):
15.06.2006 10:44
У меня она почему-то не находит элементы массива, кроме list

Конечно :-)
Потому что я не дописал

Код: Выделить всё

${ACTS[@]}
Спасибо сказали: