Как я могу заставить этот сценарий функции файла загружаться, не имея необходимость получать его каждый раз? “команда, не найденная” (основы Bash/сценариев)

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

Конечно, Вы имеете set -o noclobber (также записанный set -C) в Вашем .bashrc, право?

$ tar zc >foo.tex foo.bib
bash: foo.tex: cannot overwrite existing file

Если Вы часто даете эту команду, не вводите ее вручную: запишите make-файл.

foo.tar.gz: foo.tex foo.bib
        tar czf $@ $^

Используйте tarballs только, когда необходимо будет отправить их кому-то еще, для не создания резервных копий. Вместо того, чтобы делать резервные копии старых версий, используйте управление версиями (CVS, Подвижный, Мерзавец, и т.д., безотносительно плаваний Ваша лодка). (Действительно создайте резервную копию репозитория в случае, если Ваши дисковые сбои или Вы случайно искажаете или стираете репозиторий.)

4
18.07.2014, 01:22
2 ответа

Ваша проблема в том, что скрипт в вашем ... /bin директории выполняется в другом окружении оболочки - его окружение не переживает его выполнения, и поэтому определение x() { ... ; } не сохраняется в текущем окружении оболочки, когда он завершается.

Когда вы . ./somescript.sh (или source в некоторых оболочках, таких как bash и zsh), текущая оболочка считывает сценарий в текущее окружение и выполняет его содержимое, как если бы он был выдан из приглашения - поэтому x() { ... ; } определяется в текущем окружении оболочки - вашей интерактивной оболочке.

В принципе, проблему можно продемонстрировать следующим образом:

sh <<\HEREDOC_CMD_FILE #runs sh shell with heredoc input file
    { #begins current shell compound expression
        ( #begins subshell compound expression
            x() { echo 'I am function x.' ; } #define function x
            x #x invoked in subshell
        ) #ends subshell compound expression
        x #x invoked in current shell
    } #ends current shell compound expression
#v_end_v
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.
sh: line 6: x: command not found

Точно так же, как среда, определенная в ( : subshell) в приведенном выше примере, не переживет его завершения, не переживет ее и среда, определенная в выполняемом вами сценарии. Аналогично, когда sh читает HEREDOC_CMD_FILE, он выполняет ту же функцию, что и ваш source ../file, и запускает его содержимое в текущей среде выполнения оболочки - хотя его среда является дочерней по отношению к оболочке, выдающей приглашение, в которой он выполняется, и поэтому ни одно из его окружений не переживает его завершения оттуда. Однако вы можете заставить его сделать это, например:

. /dev/fd/0 <<\HEREDOC_CMD_FILE && x
    x() { echo 'I am function x.' ; }
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.

... что практически в точности соответствует тому, что вы делаете с source ${script}, за исключением того, что здесь мы .dot используем содержимое stdin, тогда как вы используете содержимое файла на диске

Основное различие, однако, между ( : subshell) и выполняемым скриптом заключается в том, как выглядит среда выполнения выполняемого скрипта. Когда вы выполняете сценарий, ему предоставляется новая среда для работы - поэтому любые переменные, объявленные в вашей текущей оболочке, не переносятся в ее среду, если они явно не экспортированы или не объявлены в ее командной строке. Это поведение можно продемонстрировать следующим образом:

{   x() { printf "$FMT" "$0" "$var2" x ; } #describe env
    export FMT='argv0: %s\t\tvar2: %s\t\tI am function %s.\n' \
        var1=val1 #var1 and FMT are explicitly exported
    var2=shell_val2 #var2 is not
    cat >./script && #read out stdin to >./script
        chmod +x ./script && #then make ./script executable
        var3=val3 ./script #then define var3 for ./script's env and execute
} <<\SCRIPT ; x ; y #send heredoc to block's stdin then call {x,y}()
#!/usr/bin/sh
#read out by cat to >./script then executed in a separate environment
y() { printf "$FMT" "$0" "$var2" y ; } #describe env
echo "${var1:-#var1 is unset or null}" #$var1 if not unset or null else :-this} 
echo "${var2:-#var2 is unset or null}"
echo "${var3:-#var3 is unset or null}"
export var2=script_val2
x ; y #run x() ; y() in script
#v_end_v                                                                                                                                                                                          
SCRIPT

###OUTPUT###
val1
#var2 is unset or null
val3
./script: line 8: x: command not found
argv0: ./script         var2: script_val2               I am function y.
argv0: sh               var2: shell_val2                I am function x.
sh: line 18: y: command not found

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

var1=val1 ; export var2=val2
x() { echo 'I am function x.' ; }
( 
    printf '%s\n' "$var1" "$var2" 
    x
)

###OUTPUT###
val1
val2
I am function x.

Последнее, что я должен отметить, это то, что не существует переносимого способа экспорта функций из родительской оболочки в исполняемый сценарий без .dot цитирования файла, содержащего определение функции в исполняемом сценарии, как вы это делаете с source в вашей текущей оболочке. В принципе, вы не можете переносить экспорт функции, как это можно сделать с переменными. Хотя, я полагаю, вы можете export fn_def='fn() { : fn body ; }' затем eval "$fn_def" в вашем выполняемом скрипте. И, конечно, любое дочернее окружение - исполненное или иное - умирает вместе с дочерним.

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

eval "$(cat ./script)"

Но это практически то же самое, что и . ./script - просто менее эффективно. С тем же успехом можно просто использовать исходный текст.

Лучший способ сделать это - удалить определение функции из самого скрипта и поместить его в собственный файл, а затем вызывать его как из скрипта, так и из текущей оболочки, когда он вам нужен. Например, так:

{
    echo 'x() { echo "I am function x and my argv0 is ${0}." ; }' >./fn_x                                                                                                                                                         
    echo '. ./fn_x ; x' >./script                                                                                                                                                                                                 
    chmod +x ./script && ./script                                                                                                                                                                                                 
    . ./fn_x ; x                                                                                                                                                                                                                  
}
###OUTPUT###
I am function x and my argv0 is ./script.
I am function x and my argv0 is sh.

Чтобы эта функция всегда была доступна в интерактивной оболочке, добавьте . /path/to/fn_x в файл ENV вашей оболочки. Например, для bash со сценарием, содержащим функции под названием foo, расположенным в /usr/bin, вы можете добавить эту строку в ~/.bashrc:

. /usr/bin/foo

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

7
27.01.2020, 20:47

Ответ mikeserv хорош для подробностей того, что происходит «за кулисами», но я считаю, что здесь оправдан другой ответ, поскольку он не содержит простого полезного ответа на точный вопрос заголовка:

​​Как мне загрузить функции этого файла сценария, не создавая его каждый раз?

Ответ: Загрузите его с вашего .bashrc или вашего .bash_profile , поэтому он доступен в каждой запущенной вами оболочке.

Например, у меня в .bash_profile есть следующее:

if [ -d ~/.bash_functions ]; then
  for file in ~/.bash_functions/*.sh; do
    . "$file"
  done
fi
4
27.01.2020, 20:47

Теги

Похожие вопросы