Tubulação para saída de loop impede modificação de variável local

11

Eu estou tentando escrever uma função simples do bash que usa, como argumentos, vários arquivos e / ou diretórios. Deveria:

  1. Qualifique totalmente os nomes de arquivos.
  2. Classifique-os.
  3. Remova duplicatas.
  4. Imprima tudo o que realmente existe.
  5. Retorne o número de arquivos inexistentes.

Eu tenho um script que quase faz o que eu quero, mas cai na classificação. O valor de retorno do script como está está correto, mas a saída não é (não classificada e duplicada). Se eu descomentar a | sort -uinstrução conforme indicado, a saída está correta, mas o valor de retorno é sempre 0.

NB Soluções mais simples para resolver o problema são bem-vindas, mas a questão é realmente sobre por que isso está ocorrendo no código que tenho. Ou seja, por que adicionar o pipe aparentemente interrompe o script que incrementa a variável r?

Aqui está o script:

function uniqfile
{
    local r=0 

    for arg in "$@"
    do  
        readlink -e "$arg" || (( ++r ))

    done #| sort -u    ## remove that comment

    return $r
}
tjm
fonte
Apenas uma pequena observação. Você pode reduzir for arg in "$@"para for arg. "Se 'em PALAVRAS ...;' não está presente, então 'in "$ @"' é assumido. " - help for
manatwork

Respostas:

15

Esta é uma armadilha conhecida do bash, devido a este recurso :

Cada comando em um pipeline é executado como um processo separado (isto é, em um subshell).

para que as variáveis ​​modificadas sejam locais no subshell e não sejam visíveis novamente no pai.

Para evitar isso, reformule seu código para evitar o pipeline, com uma substituição de processo:

 for arg in "$@"
    do  
        readlink -e "$arg" || (( ++r ))

    done > >(sort -u)
enzotib
fonte
Obrigado. Isso é ótimo. Gostaria de saber se você poderia me dizer o nome da >(..command..)construção. Eu acho que eu sei como ele funciona, mas sinto que eu deveria fazer alguma leitura adicional.
TJM
2
@tjm: ele é chamado de substituição processo
enzotib
Substituição processo em Bash tem muitas formas: tldp.org/LDP/abs/html/process-sub.html
SLM
A substituição de processo é uma forma de comunicação entre processos que permite que a entrada ou saída de um comando apareça como um arquivo. O comando é substituído em linha, onde um nome de arquivo normalmente ocorreria , pelo shell de comando. Isso permite que programas que normalmente só aceitam arquivos leiam ou gravem diretamente em outro programa.
No20 /
3

As | sort -uforças do bit anterior (assim que o todo for loop) para executar em um sub-processo (bash precisa de um 'STDOUT' para redirecionar para o sort'stdin'. (Internet parece pensar kshe bashlidar com este caso de forma ligeiramente diferente .. primeira ou a última comando na sequência de pipe é colocado em um subshell?)

Este tópico aborda um problema semelhante e tem uma solução interessante no final: http://ubuntuforums.org/showthread.php?t=312017

excerto
    #!/bin/bash
    exec 3< <(du | sort -n)  

    n=0
    while read size dir; do
      [ $size -gt 1000 ] && ((n++))
    done <&3
    exec 3<&-

    echo "Found $n too big files"
PT
fonte