AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 791550
Accepted
Whitehot
Whitehot
Asked: 2025-02-24 21:09:01 +0800 CST2025-02-24 21:09:01 +0800 CST 2025-02-24 21:09:01 +0800 CST

Passe vários arquivos como uma única opção

  • 772

Estou tentando construir um wrapper para executar uma ferramenta várias vezes e, em seguida, concatenar alguns dos resultados. Gostaria de passar dois conjuntos de arquivos para meu script wrapper e, em seguida, executar a ferramenta para cada par de arquivos. Gostaria que ela se comportasse assim:

multitool.sh -a a*.txt -b b*.txt(expandindo os curingas para corresponder a todos os arquivos disponíveis)

Então multitool.sh, dentro do , executo a ferramenta em a1.txt b1.txt, a2.txt b1.txt, a1.txt b2.txt, a2.txt b2.txt, etc, com números variáveis ​​de arquivos a e b.

Eu segui este tutorial explicando os conceitos básicos de manipulação de opções e consegui usar getopspara manipular um -h, mas nada mais.

É aqui que estou agora:

#!/bin/bash


while getopts ":hpg:" option; do
    case $option in 
        h) # display help
            echo "-h to print this help and exit, -p is peak files, -g is bedgraph files."
            exit;;
        p) # get list of peak files to process
            l_peaks=$OPTARG;;
        g) # get list of bedgraph files to process
            l_bgraphs=$OPTARG;;
        \?) # Invalid option
            echo "Error! Invalid option!"
            echo "-h to print this help and exit, -p is peak files, -g is bedgraph files."
            exit;;
    esac
done

echo "$l_peaks"
echo "$l_bgraphs"


Estou trabalhando com uma equipe que não tem muita familiaridade com computadores, então, se eu puder manter o wrapper em uma execução simples de uma linha, seria melhor.

Como posso passar essas listas de arquivos como uma opção cada?

shell-script
  • 4 4 respostas
  • 715 Views

4 respostas

  • Voted
  1. Best Answer
    ilkkachu
    2025-02-24T21:26:17+08:002025-02-24T21:26:17+08:00

    Não, não esmague vários nomes de arquivo em uma única string. Primeiro, porque os shells não suportam isso(*), mas também porque, em geral, nomes de arquivo podem ser quaisquer strings . Não há caractere ou sequência de caracteres que você possa usar como separador que não seja válido em um nome de arquivo. Exceto pelo byte NUL, mas você não pode passar isso como um argumento de linha de comando de qualquer maneira.

    (* Com a possível exceção do zsh.)

    Então, basta preencher dois arrays com os nomes de arquivo que você obtém como argumentos. A partir daí, também é simples fazer um loop sobre as listas.

    No entanto, getoptsele irá parar quando vir um argumento não opcional, então teremos que procurar manualmente por alguma string separadora. (O que, novamente, necessariamente é algo que poderia ser um nome de arquivo, mas pelo menos precisará corresponder a um nome de arquivo completo para que as coisas fiquem confusas. Vamos escolher ::, e se você tiver um nome de arquivo como esse, certifique-se de passá-lo como ./::.)

    #!/bin/bash
    
    while getopts "xy" option; do
        # silly placeholder
        echo "option $option"
    done
    shift $(( OPTIND - 1 ))
    
    list=a
    lista=()
    listb=()
    while [ "$1" ]; do
        case $1 in 
            ::) list=b;;
            *) if [ "$list" = a ]; then lista+=("$1")
               else                     listb+=("$1")
               fi;;
        esac
        shift
    done
    
    printf "files in list a:\n"
    printf " <%s>\n" "${lista[@]}"
    printf "\n"
    printf "files in list b:\n"
    printf " <%s>\n" "${listb[@]}"
    printf "\n"
    printf "combinations:\n"
    for b in "${listb[@]}"; do
        for a in "${lista[@]}"; do
            printf "<%s> <%s>\n" "$a" "$b"
        done
    done
    
    
    

    correndo que dá algo assim:

    $ bash lists.sh -x a* :: b*
    option x
    files in list a:
     <a1.txt>
     <a2.txt>
     <a3.txt>
    
    files in list b:
     <b1.txt>
     <b2.txt>
    
    combinations:
    <a1.txt> <b1.txt>
    <a2.txt> <b1.txt>
    <a3.txt> <b1.txt>
    <a1.txt> <b2.txt>
    <a2.txt> <b2.txt>
    <a3.txt> <b2.txt>
    

    Ajuste o loop getopts conforme necessário, mas lembre-se de manter o comando shift para limpar as opções getopts já processadas.


    Ou seja, presumi acima que você queria combinar o primeiro A com todos os Bs, e o segundo A com todos os Bs, etc.

    Se, em vez disso, você quiser o primeiro A com o primeiro B, o segundo A com o segundo B, etc., então o último loop precisaria ser algo como:

    i=0
    # stop when either list ends
    while [ "${lista[i]}" ] && [ "${listb[i]}" ]; do
        printf "<%s> <%s>\n" "${lista[i]}" "${listb[i]}"
        i=$((i + 1))
    done
    

    Porém, como @terdon observou em um comentário, o GNU parallel poderia fazer isso diretamente.

    Assim:

    $ parallel echo {1} {2} ::: a*.txt ::: b*.txt
    a1.txt b1.txt
    a1.txt b2.txt
    a2.txt b1.txt
    a2.txt b2.txt
    a3.txt b1.txt
    a3.txt b2.txt
    
    # or
    
    $ parallel echo {1} {2} ::: a*.txt :::+ b*.txt
    a1.txt b1.txt
    a2.txt b2.txt
    

    Ele passa o comando dado por um shell, se caracteres especiais precisariam de algumas aspas extras. E é claro que você precisa ter cuidado com os separadores, com esse extra +sendo bastante significativo.

    • 9
  2. Chris Davies
    2025-02-25T00:52:07+08:002025-02-25T00:52:07+08:00

    Se você estiver feliz em copiar a abordagem usada por finde citar os dois padrões glob curinga, você pode usar seu script para expandir os valores apropriadamente. Nesta configuração, os padrões citados são então manipulados pelo shell (e, portanto, por getopts) como apenas um argumento cada.

    Aqui está um exemplo, onde peguei seu código existente praticamente inalterado e o encolhi para (minha) conveniência. Seu código está melhor disposto, mas as partes interessantes são o processamento dos argumentos -aand -b:

    #!/bin/bash
    #
    # Multiple arguments: multitool -hp -a 'a*.txt' -b 'b*.txt'
    #
    ########################################################################
    #
    while getopts ':hpg:a:b:' option
    do
        case "$option" in
            a) a_list=($OPTARG) ;;    # Expand the (quoted) argument to the a_list array
            b) b_list=($OPTARG) ;;    # Expand the (quoted) argument to the b_list array
            h) echo "HELP!" >&2; exit 1 ;;
            p) l_peaks="$OPTARG" ;;
            g) l_bgraphs="$OPTARG" ;;
            \?) echo "ERROR!" >&2; exit 1 ;;
        esac
    done
    shift $(( OPTIND - 1 ))
    
    # Checking values
    #
    echo "l_peaks=$l_peaks, l_bgraphs=$l_bgraphs"
    echo "a_list=(${a_list[@]})"
    echo "b_list=(${b_list[@]})"
    echo
    
    for ((i=0; i<="${#a_list[@]}"; i++))
    do
        printf '%d\t%s\t%s\n' "$i" "${a_list[$i]}" "${b_list[$i]}"
    done
    

    Exemplo

    ls
    aapple  abanana  acherry  bone  bthree  btwo
    
    ./multitool -p -a 'a*' -b 'b*'
    l_peaks=, l_bgraphs=
    a_list=(aapple abanana acherry)
    b_list=(bone bthree btwo)
    
    0       aapple  bone
    1       abanana bthree
    2       acherry btwo
    
    • 4
  3. Stéphane Chazelas
    2025-02-25T00:27:05+08:002025-02-25T00:27:05+08:00

    Uma das abordagens comuns para comandos que lidam com opções que aceitam mais de um argumento é chamar as opções várias vezes, como em:

    cmd -vvi file1 -i file2 -ifile3 -o out1 -ifile4
    

    (acima mostrando exemplos onde o argumento é fornecido como um argumento separado ou no mesmo argumento)

    Para aplicar isso aos globs, se estiver usando zsh, você pode fazer:

    cmd -vv file*(nP[-i]) -o out1
    

    Onde o P[prefix]qualificador glob insere um argumento extra -iantes de cada arquivo gerado para que ele acabe sendo executado cmd -vv -i file1 -i file2 ... -o out.

    (observe também o nqualificador para garantir que os arquivos sejam classificados numericamente, de modo que file10venha depois de file9e não entre file1e, file2como aconteceria na ordem lexical padrão).

    Ou:

    cmd -vv file*(ne:REPLY[1,0]=-i:) -o out1
    

    Ou:

    files=(file*(n)); cmd -vv -i$^files -o out1
    

    Ou, com a histsubstpatternopção habilitada:

    cmd -vv file*(n:s/#/-i) -o out1
    

    Ter -iadicionado (no mesmo argumento) a cada nome de arquivo, para que ele acabe executando cmd -vv -ifile1 -ifile2 ... -o out1.

    No bash (ou ksh93 de onde o bash copiou essa sintaxe), você pode fazer:

    files=(file*); cmd -vv "${files[@]/#/-i}" -o out
    

    Porém, tenha cuidado, pois o bash não tem equivalente para o qualificador do zsh, nentão file10ele será classificado entre file1e file2e, a menos que você ative a failglobopção, ele será chamado cmdcom um -ifile*argumento literal se não houver nenhum arquivo correspondente.

    No cmdpróprio script, você faria algo como:

    inputs=()
    while getopts i: option; do
      case $option in
        (i) inputs+=( "$OPTARG" );;
        # rest of option parsing
      esac
    done
    echo "I got ${#inputs[@]} input files"
    
    • 3
  4. Kusalananda
    2025-02-25T01:24:34+08:002025-02-25T01:24:34+08:00

    Sabemos que a lista de arquivos será um número par, dado que eles devem ser pareados uns com os outros. Usando isso, podemos usar um loop sobre todos os nomes de arquivo fornecidos e, enquanto iteramos sobre eles, nós os contamos. Quando tivermos atingido o ponto médio, podemos começar a parear o primeiro nome na lista com o nome atual no loop, então shift(o que remove o primeiro item da lista) e repetir.

    Isso funciona porque um forloop sempre itera sobre uma lista estática. Assim, "salvamos" a lista de nomes ao fazer um loop sobre ela, e podemos modificar a lista de parâmetros posicionais dentro do loop sem perturbar o loop em si.

    #!/bin/sh
    
    n=$(( $# / 2 ))
    
    for name do
            if [ "$n" -le 0 ]; then
                    # The pair is "$1" (from the 1st set of files)
                    # and "$name" (from the 2nd set of files).
                    printf 'a-file = "%s"\tb-file = "%s"\n' "$1" "$name"
                    shift
            fi
    
            n=$(( n - 1 ))
    done
    

    Observe que nada disso requer o bashshell; podemos executá-lo com um shshell mais simples.

    Teste:

    $ ls
    a1.txt   a2.txt  a4.txt  a6.txt  a8.txt  b1.txt   b2.txt  b4.txt  b6.txt  b8.txt  script
    a10.txt  a3.txt  a5.txt  a7.txt  a9.txt  b10.txt  b3.txt  b5.txt  b7.txt  b9.txt
    
    $ sh script a* b*
    a-file = "a1.txt"       b-file = "b1.txt"
    a-file = "a10.txt"      b-file = "b10.txt"
    a-file = "a2.txt"       b-file = "b2.txt"
    a-file = "a3.txt"       b-file = "b3.txt"
    a-file = "a4.txt"       b-file = "b4.txt"
    a-file = "a5.txt"       b-file = "b5.txt"
    a-file = "a6.txt"       b-file = "b6.txt"
    a-file = "a7.txt"       b-file = "b7.txt"
    a-file = "a8.txt"       b-file = "b8.txt"
    a-file = "a9.txt"       b-file = "b9.txt"
    

    Se você precisar fazer uma análise de opções, faça isso antes do código que emparelha os nomes dos arquivos:

    #!/bin/sh
    
    myself=$0
    
    usage () {
            cat <<-USAGE_MESSAGE
            USAGE:
                $myself -h
                $myself -- {File set A} {File set B}
    
                -h          Display usage info
                File set A  Filenames for file set A
                File set B  Filenames for file set B
            USAGE_MESSAGE
    }
    
    while getopts h opt; do
            case $opt in
                    h)
                            usage
                            exit
                            ;;
                    *)
                            printf '%s: Error in option parsing\n' "$myself" >&2
                            exit 1
            esac
    done
    shift "$(( OPTIND - 1 ))"
    
    if [ "$#" -eq 0 ]; then
            usage >&2
            exit 1
    fi
    
    n=$(( $# / 2 ))
    
    for name do
            if [ "$n" -le 0 ]; then
                    printf 'a-file = "%s"\tb-file = "%s"\n' "$1" "$name"
                    shift
            fi
    
            n=$(( n - 1 ))
    done
    
    • 0

relate perguntas

  • Subtraindo a mesma coluna entre duas linhas no awk

  • Um script que imprime as linhas de um arquivo com seu comprimento [fechado]

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Dividir por delimitador e concatenar problema de string

  • MySQL Select com função IN () com array bash

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve