Seu problema é que o script no diretório .../bin
é exec
ed em outro ambiente shell - seu ambiente não sobrevive à sua execução e, portanto, a definição x() { ... ; }
não sobrevive ao ambiente shell atual quando ele é concluído .
Quando você . ./somescript.sh
(ou source
em alguns shells, como bash
e zsh
) o shell atual lê o script no ambiente atual e executa o conteúdo como se fosse emitido no prompt - então x() { ... ; }
é definido no ambiente shell atual - seu shell interativo.
Basicamente, o problema pode ser demonstrado assim:
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
Da mesma forma, o ambiente definido no ( : subshell )
no exemplo acima não sobrevive à sua conclusão, nem é definido no script executado. Da mesma forma, quando sh
lê o HEREDOC_CMD_FILE
, está executando a mesma função que seu source ../file
e executando seu conteúdo em seu ambiente de execução shell atual - embora seu ambiente seja um subshell filho para o shell que emite o prompt no qual ele é executado e, portanto, nenhum de seus ambientes sobrevive a sua conclusão a partir daí. Você pode fazer isso, no entanto, como:
. /dev/fd/0 <<\HEREDOC_CMD_FILE && x
x() { echo 'I am function x.' ; }
HEREDOC_CMD_FILE
###OUTPUT###
I am function x.
... o que é exatamente o que você faz com source ${script}
, exceto que aqui nós .dot
obtemos o conteúdo de stdin
enquanto você obtém o conteúdo de um arquivo no disco
A principal diferença, no entanto, entre um ( : subshell )
e um script executado é a aparência do ambiente de execução de um script executado. Quando você executa um script, é fornecido um ambiente novo no qual operar - portanto, quaisquer variáveis declaradas em seu shell atual não são transportadas para seu ambiente, a menos que sejam explicitamente exportadas ou declaradas em sua linha de comando. Esse comportamento pode ser demonstrado assim:
{ 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
Esse comportamento difere do de ( : subshells )
executado no prompt ou de outra forma como filhos do shell atual, porque herdam automaticamente o ambiente de seu pai, enquanto - como demonstrado acima - os filhos executados exigem que ele seja explicitamente export
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.
A última coisa que devo observar é que não há maneira portátil de exportar funções de um shell pai para um script executado sem .dot
sourcing um arquivo contendo a definição de função dentro do script executado, como você faz com source
na sua concha atual. Basicamente, você não pode portar export function
como você pode com variáveis. Porém, suponho que você possa export fn_def='fn() { : fn body ; }'
then eval "$fn_def"
em seu script executado. E, claro, qualquer ambiente infantil - executado ou não - morre com a criança.
Portanto, se você quiser a função definida no script, mas não quiser fonte do próprio script, você terá que ler a função como saída de algum comando e eval
it.
eval "$(cat ./script)"
Mas isso é praticamente a mesma coisa que . ./script
- apenas menos eficiente. Você pode muito bem apenas fornecer isso.
A melhor maneira de fazer isso é remover a definição de função do próprio script e colocá-lo em seu próprio arquivo, depois fonte-lo do seu script e do shell atual quando você quiser. Assim:
{
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.
Para que isso esteja sempre disponível em um shell interativo, adicione um . /path/to/fn_x
ao arquivo ENV
do seu shell. Por exemplo, para bash
com um script que contém funções chamadas foo
localizadas em /usr/bin
, você pode adicionar essa linha a ~/.bashrc
:
. /usr/bin/foo
Se o script for por qualquer motivo não disponível nesse local durante a inicialização, o shell ainda lerá e fornecerá o restante do arquivo ENV
conforme o esperado, embora haja uma mensagem de diagnóstico impressa em stderr
para permitir Você sabe que houve um problema ao pesquisar seu arquivo de definição de função.