Por que a fonte lib / * não funciona?

11

Eu tenho um pequeno programa que contém a seguinte estrutura de pastas:

- main.sh
- lib/
  - clean.sh
  - get.sh
  - index.sh
  - test.sh

Cada arquivo contém uma única função na qual eu uso main.sh.

main.sh:

source lib/*

get_products
clean_products
make_index
test_index

Acima, as duas primeiras funções funcionam, mas as duas seguintes não.

No entanto, se eu substituir source lib/*por:

source lib/get.sh
source lib/clean.sh
source lib/index.sh
source lib/test.sh

Tudo funciona como esperado.

Alguém sabe por source lib/*que não funciona como o esperado?

Philip Kirkbride
fonte
2
Não respondendo à pergunta, se você quiser fazê-lo em uma linha, veja /etc/bashrccomo ele usa um forloop para lidar /etc/profile.d/*.sh. Se você confia no conteúdo, lib/ele pode ser reduzido a uma linha:for i in lib/*.sh; do . "$i"; done
Rich

Respostas:

21

O Bash sourceembutido usa apenas um único nome de arquivo:

source filename [arguments]

Qualquer coisa além do primeiro parâmetro se torna um parâmetro posicional para filename.

Uma ilustração simples:

$ cat myfile
echo "param1: $1"
$ source myfile foo
param1: foo

Saída total de help source

source: source filename [arguments]

Execute commands from a file in the current shell.

Read and execute commands from FILENAME in the current shell.  The
entries in $PATH are used to find the directory containing FILENAME.
If any ARGUMENTS are supplied, they become the positional parameters
when FILENAME is executed.

Exit Status:
Returns the status of the last command executed in FILENAME; fails if
FILENAME cannot be read.

(Isso também se aplica à "fonte de pontos" equivalente embutida .que, vale a pena notar, é a maneira POSIX e, portanto, mais portátil.)

Quanto ao comportamento aparentemente contraditório que você está vendo, pode tentar executar o main.sh depois de fazer set -x. Ver quais instruções estão sendo executadas e quando pode fornecer uma pista.

Camada B
fonte
7

A documentação do Bash indica que sourcefunciona em um único nome de arquivo :

. (um periodo)

. filename [argumentos]

Leia e execute comandos do argumento filename no contexto atual do shell. Se nome do arquivo ...

E o código fonte ... para fonte ... confirma isso:

result = source_file (filename, (list && list->next));

Onde source_fileestá definido evalfile.cpara chamar _evalfile:

rval = _evalfile (filename, flags);

e _evalfileabre apenas um único arquivo:

fd = open (filename, O_RDONLY);
Jeff Schaller
fonte
5

Complementando a resposta útil da b-layer , eu sugeriria nunca usar uma expansão gananciosa da glob se você não tiver certeza se os arquivos do tipo que estão tentando expandir estão lá.

Quando você fez abaixo, existe a possibilidade de um arquivo (sem .shextensão) apenas um arquivo temporário contendo alguns comandos prejudiciais (por exemplo rm -rf *) que podem ser executados (assumindo que eles tenham permissões de execução)

source lib/*

Sempre faça a expansão glob com o conjunto de limites adequado, no seu caso, embora você possa fazer um loop apenas nos *.sharquivos

for globFile in lib/*.sh; do
    [ -f "$globFile" ] || continue
    source "$globFile"
done

Aqui, [ -f "$globFile" ] || continueele cuidaria do retorno do loop se nenhum padrão glob corresponder na pasta atual, ou seja, equivalente às opções estendidas do shell nullglobno bashshell.

Inian
fonte
Usar a substituição de processo com cattambém funcionaria:source <(cat lib/*.sh)
Xophmeister 4/17
@Xophmeister, ... por um valor mais limitado para "trabalho". Se você tentou depurar com set -xe um PS4que coloca BASH_SOURCEe LINENOem seus logs, não era mais possível ver de qual arquivo e linha um determinado comando vem.
Charles Duffy
2
@Xophmeister, ... também, um script pode causar um curto-circuito na sua execução return. Após essa prática, qualquer script que faça isso impediria a execução de todos os seguintes.
Charles Duffy
1
Isso é bem próximo de como é feito /etc/bashrcquando é processado /etc/profile.d/*.sh.
Rich