В Bash, есть ли способ дважды раскрыть переменные в двойных кавычках?

Для отладки моих сценариев я хотел бы добавить внутренние переменные $FUNCNAME и $LINENO в начале каждого из моих выходных данных, чтобы я знал, на какую функцию и номер строки выводятся выходные данные.

foo(){
    local bar="something"
    echo "$FUNCNAME $LINENO: I just set bar to $bar"
}

Но так как будет много отладочных выходных данных, было бы чище, если бы я мог сделать что-то вроде следующего:

foo(){
    local trace='$FUNCNAME $LINENO'
    local bar="something"
    echo "$trace: I just set bar to $bar"
}

Но вышеприведенное буквально выдает: "$FUNCNAME $LINENO: я просто установил что-то в строку". Я думаю, что это происходит потому, что двойные кавычки только один раз расширяют переменные внутри.

Есть ли синтаксически чистый способ расширения переменных дважды в одной строке?

2 ответа

Решение

Вы не можете безопасно оценить расширения дважды при обработке данных времени выполнения.

Существуют средства для повторной оценки, но они требуют доверия к вашим данным - в смысле конструкции системы АНБ: "Надежный компонент - это тот, который может сломать вашу систему в случае сбоя".

Смотрите BashFAQ #48 для подробного обсуждения. Имейте в виду, что если вы можете записывать имена файлов, то любой символ, кроме NUL, может присутствовать в имени файла UNIX. $(rm -rf ~)'$(rm -rf ~)'.txt это официальное название. * это официальное название.

Рассмотрим другой подход:

#!/usr/bin/env bash

trace() { echo "${FUNCNAME[1]}:${BASH_LINENO[0]}: $*" >&2; }

foo() {
        bar=baz
        trace "I just set bar to $bar"
}

foo

... который при запуске с bash 4.4.19(1)-релизом выдает:

foo:7: I just set bar to baz

Обратите внимание на использование ${BASH_LINENO[0]} а также ${FUNCNAME[1]}; это потому что BASH_LINENO определяется следующим образом:

Переменная массива, членами которой являются номера строк в исходных файлах, где был вызван каждый соответствующий член FUNCNAME.

Таким образом, FUNCNAME[0] является trace, в то время как FUNCNAME[1] является foo; в то время как BASH_LINENO[0] это линия, с которой trace была вызвана - строка, которая находится внутри функции foo,

Да для двойного расширения; но нет, он не сделает того, на что вы надеетесь.

Да, bash предлагает способ "двойного раскрытия" переменной, иначе говоря, способ сначала интерпретировать переменную, а затем принять это как имя какой-то другой переменной, а другая переменная - это то, что на самом деле должно быть расширено. Это называется "косвенное обращение". При "косвенном обращении" bash позволяет переменной оболочки ссылаться на другую переменную оболочки, причем конечное значение берется из переменной, на которую указывает ссылка. Итак, переменную bash можно передать по ссылке.

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

${!VARNAME}

Он используется так:

BAR="my final value";
FOO=BAR
echo ${!FOO};

... который производит этот вывод...

my final value

Нет, вы не можете использовать этот механизм для того же, что и $( eval "echo $VAR1 $VAR2"). Результатом первой интерпретации должно быть в точности имя переменной оболочки. Он не принимает строку и не понимает знака доллара. Так что это не сработает:

BAR="my final value";
FOO='$BAR'; # The dollar sign confuses things
echo ${!FOO}; # Fails because there is no variable named '$BAR'

Итак, это не решает ваш окончательный квест. Тем не менее косвенное обращение может быть мощным инструментом.

Хотя eval имеет свои опасности, получение второго расширения это то, что он делает:

foo(){
    local trace='$FUNCNAME $LINENO'
    local bar="something"
    eval echo "$trace: I just set bar to $bar"
}

foo

дает:

foo 6: I just set bar to something

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

Другие вопросы по тегам