While loop para bash scripting para ler stdin ou argumentos

0

Estou brincando com a resposta aceita deste tópico: Script de bash que lê nomes de arquivos de um pipe ou de argumentos de linha de comando?

Quando uso o script abaixo, efetch ( ftp: //ftp.ncbi.nlm. nih.gov/entrez/entrezdirect/edirect.zip ) aceita um id (argumento, por exemplo, 941241313), mas não stdin.

if [ $# -gt 0 ] ;then
    for name in "$@"; do
        efetch -db nucleotide -id $name -format gpc > $name.xml;
    done
  else
    IFS=$'\n' read -d '' -r -a filenames
    while read name; do
        efetch -db nucleotide -id $name -format gpc > $name.xml;
    done < "${filenames[@]}"
  fi

Quando eu modifico para a versão abaixo, efetch aceita stdin em vez de um id

if [ $# -gt 0 ] ;then
    while read name; do
        efetch -db nucleotide -id $name -format gpc > $name.xml;
    done < "$@"
  else
    IFS=$'\n' read -d '' -r -a filenames
    while read name; do
        efetch -db nucleotide -id $name -format gpc > $name.xml;
    done < "${filenames[@]}"
  fi

O que há de errado?

    
por ahelix 12.11.2015 / 06:36

2 respostas

1

A mudança é para este pedaço

while read name; do
    efetch -db nucleotide -id $name -format gpc > $name.xml;
done < "$@"

que torna o efetch no loop executado com sua entrada padrão redirecionada para o arquivo fornecido pelos argumentos. Então, isso faz duas alterações na maneira como o efetch é usado:

  • sua entrada padrão não é mais o padrão (terminal)
  • sua lista de parâmetros não é mais literalmente os parâmetros da linha de comando para o script, mas indiretamente, de um arquivo.

Se efetch detectar que sua entrada não é um terminal, ela poderá reabrir o terminal diretamente (talvez seja a isso que você está se referindo como "efetch aceita stdin em vez de um id"). Alternativamente, se efetch estiver lendo seu stdin, ele poderá ler algo inesperado (em um teste rápido, esse parece ser o próprio script).

@chepner apontou que o shell (bash neste caso) não gera um subprocesso para o loop . Eu tinha em mente um caso diferente que faz. Considere estes dois scripts:

#!/bin/bash 
LAST=...
while read name
do
    /bin/echo "** $name"
    LAST="$name"
done < "$@"
echo "...$LAST"

e

#!/bin/bash
LAST=...
cat "$@" | while read name
do
    /bin/echo "** $name"
    LAST="$name"
done
echo "...$LAST"

O último (pipe) irá ecoar "......" no final, enquanto o antigo (redirecionamento) ecoa a última variável atribuída a LAST dentro do loop. O formulário que usa um pipe às vezes é comentado exigindo que um subprocesso seja responsável pelo motivo pelo qual as atribuições de variáveis não são propagadas fora do loop.

Curiosamente, existem diferenças entre os shells para o último (um pipe) em relação ao número de processos utilizados. Testando com (Debian / testing) bash, traço (/ bin / sh), zsh e ksh93, usando strace -fo para capturar chamadas de sistema e IDs de processo:

#!/bin/sh
for sh in bash dash zsh ksh93
do
    echo "++ $sh"
    strace -fo $sh.log ./do-$sh ./once
    LC=$(sed -e 's/ .*//' $sh.log |sort -u |wc -l)
    WC=$(wc -l $sh.log)
    echo "-- $LC / $WC"
done

O script mostra o número de processos e o número de chamadas do sistema para cada shell. (O arquivo once contém duas linhas: "first" e "second", para eliminar um limite de teste).

Eu vejo que o zsh e o ksh93 usam um processo a menos que o bash e o dash:

$ ./testit
++ bash
** first
** second
......
-- 5 / 401 bash.log
++ dash
** first
** second
......
-- 5 / 222 dash.log
++ zsh
** first
** second
...second
-- 4 / 568 zsh.log
++ ksh93
** first
** second
...second
-- 4 / 336 ksh93.log

A execução do pipe requer mais 1 ou 2 processos do que usar um documento here para este exemplo.

    
por 12.11.2015 / 10:34
0

Meu erro, como apontado por @chepner, esse bloco de código não está funcionando. Por algumas razões, meu roteiro foi quebrado. Foi feito usando nano em uma sessão de tela GNU (em um servidor). O efetch ainda pode ser executado após as linhas terem sido comentadas!

IFS=$'\n' read -d '' -r -a filenames
while read name; do
    efetch -db nucleotide -id $name -format gpc > $name.xml;
done < "${filenames[@]}"

O script abaixo é feio, mas funciona.

#!/bin/bash
if [ $# -eq 0 ]; then
    echo "Please provide either a GI or a list of GI (one per line) in a file!"
elif [ -r "$@" ]; then
    while read name; do
        efetch -db nucleotide -id $name -format gpc > $name.xml;
    done < "$@"
else
    efetch -db nucleotide -id "$@" -format gpc > "$@".xml;
fi
    
por 13.11.2015 / 04:25