Existe um bash equivalente a um objeto de pipeline do PowerShell?

1

No PowerShell, eu poderia executar algo assim em uma única linha

Get-ChildItem | foreach {
  if ($_ -match '.+?\.py$' -eq $true) {
    $_ | Do-Thing -WithFile
  }
  if ($_ -match '.+?\.pdf$' -eq $true) {
    $_ | Do-Thing -WithOtherFile
  }
}

igual a

Get-ChildItem | foreach { if ($_ -match '.+?\.py$' -eq $true) { $_ | Do-Thing -WithFile } if ($_ -match '.+?\.pdf$' -eq $true) { $_ | Do-Thing -WithOtherFile } }

A funcionalidade específica disso que eu aprecio é poder fazer referência ao objeto de pipeline via $_para ser usado no próximo comando e ser capaz de executar execução condicional com base em partes divididas de saída via | foreach {}.

Eu sei que no bash seria fácil receber vários padrões de uma só vez via

> ls | egrep '\.py$|\.pdf$'

some_foo.py
some_bar.py
foo.pdf
bar.pdf

É possível estender isso em um liner com o que está disponível por padrão, onde eu seria capaz de fazer o equivalente a

| foreach { if ($_ -match '.+?\.py$') {Do-Thing -With $_ } if (...) { Do-Thing -WithOther $_ } }

ou o Bash redireciona apenas o stdout de um comando para o stdin de outro? Acho muitos usos diversos para essa dinâmica no PowerShell, mas não ouvi falar de um equivalente no Bash.

Não é nada que bloquear e escrever um script na sintaxe usual funcione bem, fiquei curioso porque é incrivelmente útil no PS.

Procurando por: $_ , | foreach {}equivalentes onde eu poderia dividir a saída, verificar as condições e, em seguida, ser capaz de referenciar a parte da saída dividida por algum nome. No caso dos PSs, $ _

Eu sei que existem muitas maneiras pelas quais o PS é fundamentalmente diferente do Bash, então eu não ficaria surpreso se isso fosse específico do PS.

Para um melhor contexto, veja um exemplo no PS, onde você pesquisaria recursivamente arquivos com extensões python e pdf e faria um registro deles em seus respectivos arquivos de texto

Get-ChildItem -Recurse | foreach {
  if ($_ -match '.+?\.py$' -eq $true) {
    $_ | Out-File -encoding utf8 -Append py_list.txt
  }
  if ($_ -match '.+?\.pdf$' -eq $true) {
    $_ | Out-File -encoding utf8 -Append pdf_list.txt
  }
}
saniboy
fonte
Normalmente, findé a "Ferramenta certa para o trabalho", quando o trabalho é definido como "faça algo com vários arquivos, possivelmente correspondendo a um padrão, recorrendo a subdiretórios". Se você quisesse, por exemplo, torcer pelos scripts Python e vaiar nos PDFs, por exemplo, você poderia. find . -name '*.py' -exec echo 'Yay, {}!' \; -o -name '*.pdf' -exec echo 'Boo, {}!' \;Isso não depende de nenhum recurso especial que apenas o GNU findpossui (portanto, ele funcionará imediatamente no macOS, por exemplo).
TheDudeAbides

Respostas:

2

Sim (quase) e não. O Bash não suporta 'objetos' de pipelining em primeiro lugar - seus pipelines, mesmo internos, ainda são construídos em subprocessos e stdio comum (que é um bytestream) e, portanto, você só pode interagir com linhas de texto comuns (por exemplo, uma linha lista por caminhos de arquivos), não objetos complexos.

Então, o equivalente mais próximo seria while read -r <var>; do ...; done. Mais sobre isso mais tarde.


Sua tarefa específica pode ser melhor gerenciada usando um for <var> in <words>loop simples com curingas :

for file in *; do
    if [[ $file == *.py ]]; then
        do_something_with "$file"
    elif [[ $file == *.pdf ]]; then
        do_something_else --with "$file"
    fi
done

Você nem precisa reproduzir as verificações explícitas - você pode ter dois loops:

for file in *.py; do
    do_something_with "$file"
done

for file in *.pdf; do
    do_something_else --with "$file"
done

Para fazer o mesmo recursivamente , você pode a) usar os curingas recursivos de Bash:

shopt -s globstar

for file in **/*; do
    if ...; then ...; fi
done

ou b) use finde faça um loop sobre cada linha em stdin :

find . -type f | while read -r file; do
    if [[ $file == *.py ]]; then
        ...
    fi
done

(Sim, eu deveria ter usado IFS="" read -r <var>nomes de arquivos que terminam com um espaço - mas, felizmente, não tenho nenhum no meu sistema para não incomodar.)

Mais uma vez, você pode pular as verificações manuais de nome de arquivo e solicitar o que precisa com antecedência.

shopt -s globstar

for file in **/*.pdf; do
    do_something_with "$file"
done

Variante b:

find . -type f -name "*.pdf" | while read -r file; do
    do_something_with "$file"
done

O mesmo, mas usando a opção find 's -exec:

find . -type f -name "*.pdf" -exec do_something_with {} \;
gravidade
fonte
Essa foi uma resposta excelente e informativa e estou sempre agradecido por poder recorrer à comunidade de SO por respostas bem diferenciadas. Obrigado por esclarecer isso!
saniboy
Uma resposta exemplar e para a qual eu vou apontar totalmente para meus colegas da próxima vez que me perguntam "como faço x com *.yyy(possivelmente descendo em subdiretórios) em um script de shell do Bash?" ;)
TheDudeAbides