A module
função do pacote Environment Modules 1 faz seu trabalho modificando várias variáveis de ambiente do processo shell atual .
Infelizmente, essa função retorna 0 se for bem-sucedida ou não 2 , o que torna difícil para um script de cliente responder adequadamente a falhas.
Eu gostaria de implementar um wrapper de função mymodule
que module
passa todos os seus argumentos diretamente para module
e retorna corretamente um valor diferente de zero se module
falhar.
A única maneira de mymodule
detectar se module
falhou é inspecionar a função de saída module
grava em stderr, se houver.
O problema é que não consigo encontrar uma maneira razoável mymodule
de obter essa saída sem anular module
as ações de . Mais especificamente, quase todas as maneiras em que posso pensar para capturar module
o stderr de 's em uma variável envolvem a execução module
em um processo filho, impedindo-o de fazer seu trabalho (o que requer a modificação do shell atual).
A única exceção ao acima seria redirecionar module
o stderr de 's para um arquivo temporário, mas eu odeio a ideia de criar um arquivo toda vez que a module
função for executada.
Existe alguma maneira de mymodule
invocar module
no ambiente atual e ao mesmo tempo capturar seu stderr em uma variável?
Estou interessado em respostas para ambos zsh
e bash
.
1 Não confundir com o pacote de módulos de ambiente Lmod , que possui uma interface muito semelhante.
2 Pelo menos este é o caso da versão antiga 3.2.9 com a qual devo trabalhar. Eu não tenho controle sobre isso.
Tanto o Bash quanto o zsh têm coprocessos (infelizmente um pouco diferentes), que basicamente encerram uma
pipe
chamada e geram um subprocesso com sua entrada e saída padrão disponíveis para o shell de chamada. Em essência, eles permitem que o shell seja reproduzidox
em , mas com todosy
, e executando a partir do processo atual e não em um ambiente de execução de pipeline.x | cmd | y
x
y
cmd
Isso nos permitirá executar
module 2>&...
alguns...
,module
executando a partir do shell atual. Se usarmoscat
como nosso coprocesso (ou sejacmd
, ), ele apenas repetirá tudo de volta novamente e, em seguida, poderemos ler a saída de volta no shell atual novamentey <&...
mais tarde.Outra opção é redirecionar o erro padrão para outro processo em segundo plano e
wait
para seu código de retorno. Vou abordar ambos abaixo.Vou usar esta
module
função falsa para testar para que eu possa ativar e desativar erros à vontade. If modifica o ambiente shell atual para que possamos vê-lo e gera "err" para stderr; Estarei comentando essa linha dentro e fora conforme necessário:Se eu executar um
cat
coprocesso no Bash, posso redirecionarmodule
o stderr de 's para isso e lercat
o stdout de 's para fazer o que eu quiser:Em zsh precisa ser
no meio em vez disso.
Em ambos os casos, posso executar o
module
comando no shell atual e ler a saída de erro lá. Uma função pode retornar dentro doif
normal.Tudo, exceto
cat
está sendo executado a partir do ambiente de execução atual: os redirecionamentos FD não criam ambientes independentes como os pipelines. Podemosecho $FOO
no final verificar isso, e ver se a data foi atualizada porquemodule
executou no ambiente atual.Alternativamente, o processo em segundo plano poderia fazer todo o trabalho. Isso funciona no Bash:
O resultado acima será
7
ou0
de acordo com o que o subprocesso no topo disse - você pode ajustar para fazer o que quiser sobre o código de retorno. Sob zsh, não, porque$!
não está definido para substituições de processos; isso deveria ser solucionável, mas eu parei de tentar. Um fifo fixo, em vez de um arquivo temporário, também funcionaria aqui.Nesse caso, você provavelmente deseja salvar e restaurar o FD 2 em ambos os lados também.
Não sei como os "Módulos de ambiente" funcionam, mas assumirei pela sua descrição que são funções de shell que definem variáveis de ambiente e sua saída stderr deve ser capturada / correspondida sem executá-las em um processo separado.
A resposta, goste ou não, é que a única maneira robusta e óbvia é redirecionar seu stderr para um arquivo temporário. Usar pipes nomeados é tão complicado (você ainda precisa criar um arquivo temporário!), além de ser muito mais complicado. E o uso de coprocessos é oneroso, incômodo e não portátil .
Em
bash
(ebash
apenas em) você pode tirar proveito de um recurso não documentado ($!
sendo definido para o PID de uma>(...)
substituição de processo) e se safar com algo como:Este exemplo assume que
module
ele próprio não está gerando nenhum filho que possa confundir$!
.No Linux, com bash e zsh, você deve ser capaz de fazer:
O
3<<< ''
é uma string here contendo inicialmente uma linha vazia. Amboszsh
ebash
implementam here-strings e here-documents como arquivos temporários excluídos. No Linux (e Cygwin, mas geralmente não em outros sistemas), a abertura/dev/fd/3
abre o arquivo apontado pelo fd 3, mesmo que já tenha sido excluído (em outros sistemas, duplica o fd 3), por isso é uma maneira muito limpa de trabalhar com arquivos temporários lá. O arquivo já foi deletado, você não precisa se preocupar com sua limpeza, e ele só fica visível no FS por um tempo muito curto (desde a versão 5, no entanto,bash
remove as permissões de gravação para ele com as quais precisamos trabalharchmod
).Aqui, você precisa de um arquivo temporário se for executar
module
egrep
em sequência (em oposição a em paralelo em processos separados). Fazer isso com tubos (como nas abordagens de Michael, mas ao contrário do @mosvy) levaria a bloqueios se houver saída de dados suficiente para preencher os tubos.