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 / 497985
Accepted
Levi Uzodike
Levi Uzodike
Asked: 2019-02-01 11:10:41 +0800 CST2019-02-01 11:10:41 +0800 CST 2019-02-01 11:10:41 +0800 CST

A tubulação, deslocamento ou expansão de parâmetros é mais eficiente?

  • 772

Estou tentando encontrar a maneira mais eficiente de iterar certos valores que são um número consistente de valores distantes um do outro em uma lista de palavras separadas por espaço (não quero usar uma matriz). Por exemplo,

list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"

Então, eu quero poder apenas iterar na lista e acessar apenas 1,5,6,9 e 15.

EDIT: Eu deveria ter deixado claro que os valores que estou tentando obter da lista não precisam ser diferentes em formato do resto da lista. O que os torna especiais é apenas sua posição na lista (neste caso, posição 1,4,7...). Então a lista poderia ser1 2 3 5 9 8 6 90 84 9 3 2 15 75 55, mas eu ainda gostaria dos mesmos números. E também, quero poder fazer isso assumindo que não sei o tamanho da lista.

Os métodos que eu pensei até agora são:

Método 1

set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
    if [ "${@:count:1}" -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
done

Método 2

set list
found=false
find=9
while [ $# ne 0 ]; do
    if [ $1 -eq $find ]; then
    found=true
    break
    fi
    shift 3
done

Método 3 Tenho certeza de que a tubulação torna essa a pior opção, mas eu estava tentando encontrar um método que não usasse set, por curiosidade.

found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
    if [ $num -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
    num=`echo $list | cut -d ' ' -f$count`
done

Então, o que seria mais eficiente, ou estou perdendo um método mais simples?

shell-script pipe
  • 8 8 respostas
  • 3466 Views

8 respostas

  • Voted
  1. ilkkachu
    2019-02-01T11:33:57+08:002019-02-01T11:33:57+08:00
    • Primeira regra de otimização de software: não .

      Até que você saiba que a velocidade do programa é um problema, não há necessidade de pensar em quão rápido ele é. Se sua lista tiver esse tamanho ou apenas cerca de 100 a 1.000 itens, você provavelmente nem perceberá quanto tempo leva. Há uma chance de você estar gastando mais tempo pensando na otimização do que na diferença.

    • Segunda regra: Medir .

      Essa é a maneira certa de descobrir, e aquela que dá respostas para o seu sistema. Especialmente com conchas, existem muitas e nem todas são idênticas. Uma resposta para um shell pode não se aplicar ao seu.

      Em programas maiores, a criação de perfil também ocorre aqui. A parte mais lenta pode não ser a que você pensa que é.

    • Terceiro, a primeira regra de otimização de script de shell: não use o shell .

      Sim, realmente. Muitos shells não são feitos para serem rápidos (já que o lançamento de programas externos não precisa ser), e eles podem até analisar as linhas do código-fonte novamente a cada vez.

      Use algo como awk ou Perl. Em um micro-benchmark trivial que fiz, awkfoi dezenas de vezes mais rápido do que qualquer shell comum na execução de um loop simples (sem E/S).

      No entanto, se você usar o shell, use as funções internas do shell em vez de comandos externos. Aqui, você está usando o exprque não está embutido em nenhum shell que encontrei no meu sistema, mas que pode ser substituído pela expansão aritmética padrão. Por exemplo i=$((i+1)), em vez de i=$(expr $i + 1)incrementar i. Seu uso de cutno último exemplo também pode ser substituído por expansões de parâmetros padrão.

      Veja também: Por que usar um loop de shell para processar texto é considerado uma prática ruim?

    Os passos 1 e 2 devem se aplicar à sua pergunta.

    • 36
  2. Best Answer
    DopeGhoti
    2019-02-01T11:21:02+08:002019-02-01T11:21:02+08:00

    Bem simples com awk. Isso fornecerá o valor de cada quarto campo para entrada de qualquer comprimento:

    $ awk -F' ' '{for( i=1;i<=NF;i+=3) { printf( "%s%s", $i, OFS ) }; printf( "\n" ) }' <<< $list
    1 5 6 9 15
    

    Isso funciona aproveitando awkvariáveis ​​internas como NF(o número de campos no registro) e fazendo alguns loops simples forpara iterar ao longo dos campos para fornecer os que você deseja sem precisar saber com antecedência quantos serão.

    Ou, se você realmente deseja apenas esses campos específicos, conforme especificado em seu exemplo:

    $ awk -F' ' '{ print $1, $4, $7, $10, $13 }' <<< $list
    1 5 6 9 15
    

    Quanto à questão sobre eficiência, o caminho mais simples seria testar este ou cada um de seus outros métodos e usar timepara mostrar quanto tempo leva; você também pode usar ferramentas como stracever como as chamadas do sistema fluem. Uso de timese parece com:

    $ time ./script.sh
    
    real    0m0.025s
    user    0m0.004s
    sys     0m0.008s
    

    Você pode comparar essa saída entre vários métodos para ver qual é o mais eficiente em termos de tempo; outras ferramentas podem ser usadas para outras métricas de eficiência.

    • 18
  3. Gilles 'SO- stop being evil'
    2019-02-01T23:59:03+08:002019-02-01T23:59:03+08:00

    Vou apenas dar alguns conselhos gerais nesta resposta, e não referências. Os benchmarks são a única maneira de responder de forma confiável a perguntas sobre desempenho. Mas como você não diz quantos dados você está manipulando e com que frequência você executa essa operação, não há como fazer um benchmark útil. O que é mais eficiente para 10 itens e o que é mais eficiente para 1000.000 itens geralmente não é o mesmo.

    Como regra geral, invocar comandos externos é mais caro do que fazer algo com construções de shell puro, desde que o código de shell puro não envolva um loop. Por outro lado, um loop de shell que itera em uma string grande ou em uma grande quantidade de string provavelmente será mais lento do que uma invocação de uma ferramenta de propósito especial. Por exemplo, sua invocação de loop cutpode ser notavelmente lenta na prática, mas se você encontrar uma maneira de fazer tudo com uma única cutinvocação, provavelmente será mais rápido do que fazer a mesma coisa com a manipulação de strings no shell.

    Observe que o ponto de corte pode variar muito entre os sistemas. Pode depender do kernel, de como o escalonador do kernel está configurado, do sistema de arquivos que contém os executáveis ​​externos, de quanta pressão de CPU vs memória existe no momento e muitos outros fatores.

    Não ligue exprpara fazer aritmética se estiver preocupado com o desempenho. Na verdade, não ligue exprpara fazer cálculos aritméticos. Os shells têm aritmética integrada, que é mais clara e rápida do que invocar expr.

    Você parece estar usando bash, já que está usando construções bash que não existem em sh. Então, por que diabos você não usaria uma matriz? Uma matriz é a solução mais natural e provavelmente a mais rápida também. Observe que os índices de matriz começam em 0.

    list=(1 2 3 5 9 8 6 90 84 9 3 2 15 75 55)
    for ((count = 0; count += 3; count < ${#list[@]})); do
      echo "${list[$count]}"
    done
    

    Seu script pode ser mais rápido se você usar sh, se seu sistema tiver dash ou ksh as em shvez de bash. Se você usar sh, não obterá arrays nomeados, mas ainda obterá um array de parâmetros posicionais, que pode ser definido com set. Para acessar um elemento em uma posição que não é conhecida até o tempo de execução, você precisa usar eval(cuide de citar as coisas corretamente!).

    # List elements must not contain whitespace or ?*\[
    list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
    set $list
    count=1
    while [ $count -le $# ]; do
      eval "value=\${$count}"
      echo "$value"
      count=$((count+1))
    done
    

    Se você quiser acessar o array apenas uma vez e estiver indo da esquerda para a direita (pulando alguns valores), você pode usar shiftem vez de índices de variáveis.

    # List elements must not contain whitespace or ?*\[
    list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
    set $list
    while [ $# -ge 1 ]; do
      echo "$1"
      shift && shift && shift
    done
    

    Qual abordagem é mais rápida depende do shell e do número de elementos.

    Outra possibilidade é usar o processamento de strings. Tem a vantagem de não usar os parâmetros posicionais, então você pode usá-los para outra coisa. Será mais lento para grandes quantidades de dados, mas é improvável que faça uma diferença notável para pequenas quantidades de dados.

    # List elements must be separated by a single space (not arbitrary whitespace)
    list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
    while [ -n "$list" ]; do
      echo "${list% *}"
      case "$list" in *\ *\ *\ *) :;; *) break;; esac
      list="${list#* * * }"
    done
    
    • 14
  4. bishop
    2019-02-02T11:08:23+08:002019-02-02T11:08:23+08:00

    awké uma ótima escolha, se você puder fazer todo o seu processamento dentro do script Awk. Caso contrário, você acaba canalizando a saída do Awk para outros utilitários, destruindo o ganho de desempenho do awk.

    basha iteração sobre uma matriz também é ótima, se você puder encaixar sua lista inteira dentro da matriz (o que para shells modernos provavelmente é uma garantia) e não se importar com a ginástica de sintaxe de matriz.

    No entanto, uma abordagem de pipeline:

    xargs -n3 <<< "$list" | while read -ra a; do echo $a; done | grep 9
    

    Onde:

    • xargsagrupa a lista separada por espaços em branco em lotes de três, cada nova linha separada
    • while readconsome essa lista e gera a primeira coluna de cada grupo
    • grepfiltra a primeira coluna (correspondente a cada terceira posição na lista original)

    Melhora a compreensão, na minha opinião. As pessoas já sabem o que essas ferramentas fazem, então é fácil ler da esquerda para a direita e raciocinar sobre o que vai acontecer. Essa abordagem também documenta claramente o comprimento da passada ( -n3) e o padrão de filtro ( 9), por isso é fácil variar:

    count=3
    find=9
    xargs -n "$count" <<< "$list" | while read -ra a; do echo $a; done | grep "$find"
    

    Quando fazemos perguntas de "eficiência", não se esqueça de pensar em "eficiência total ao longo da vida". Esse cálculo inclui o esforço dos mantenedores para manter o código funcionando, e nós, sacos de carne, somos as máquinas menos eficientes em toda a operação.

    • 3
  5. doneal24
    2019-02-01T11:21:41+08:002019-02-01T11:21:41+08:00

    Talvez isso?

    cut -d' ' -f1,4,7,10,13 <<<$list
    1 5 6 9 15
    
    • 2
  6. KamilCuk
    2019-02-01T11:19:38+08:002019-02-01T11:19:38+08:00

    Não use comandos shell se quiser ser eficiente. Limite-se a pipes, redirecionamentos, substituições, etc., e programas. É por isso que xargsexistem parallelutilitários - porque bash while loops são ineficientes e muito lentos. Use loops bash apenas como a última resolução.

    list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
    if 
        <<<"$list" tr -d -s '[0-9 ]' | 
        tr -s ' ' | tr ' ' '\n' | 
        grep -q -x '9'
    then
        found=true
    else 
        found=false
    fi
    echo ${found} 
    

    Mas você provavelmente deve ficar um pouco mais rápido com bom awk.

    • 1
  7. user000001
    2019-02-03T08:43:31+08:002019-02-03T08:43:31+08:00

    Na minha opinião, a solução mais clara (e provavelmente a de melhor desempenho também) é usar as variáveis ​​RS e ORS awk:

    awk -v RS=' ' -v ORS=' ' 'NR % 3 == 1' <<< "$list"
    
    • 1
  8. agc
    2019-02-04T19:25:40+08:002019-02-04T19:25:40+08:00
    1. Usando o shell script GNU sed e POSIX :

      echo $(printf '%s\n' $list | sed -n '1~3p')
      
    2. Ou com basha substituição de parâmetro de :

      echo $(sed -n '1~3p' <<< ${list// /$'\n'})
      
    3. Não - GNU ( ou seja , POSIX ) sed, e bash:

      sed 's/\([^ ]* \)[^ ]* *[^ ]* */\1/g' <<< "$list"
      

      Ou mais portátil, usando ambos POSIX sed e script de shell:

      echo "$list" | sed 's/\([^ ]* \)[^ ]* *[^ ]* */\1/g'
      

    Saída de qualquer um destes:

    1 5 6 9 15
    
    • 1

relate perguntas

  • 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

  • ordem de substituição de processos `te` e `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