Explicação do uso de 'sed' em script de shell específico

1

Ao ler um tutorial on-line , deparei-me com o seguinte código :

#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of

if [ $# -lt 1 ]
then
  echo "Usage: $0 file ..."
  exit 1
fi

echo "$0 counts the lines of code" 
l=0
n=0
s=0
for f in $*
do
  if [ -O $f ] # checks whether file owner is running the script
  then 
      l='wc -l $f | sed 's/^\([0-9]*\).*$//''
      echo "$f: $l"
      n=$[ $n + 1 ]
      s=$[ $s + $l ]
  else
      continue
  fi
done

echo "$n files in total, with $s lines in total"

Qual é o objetivo da chamada sed neste exemplo?

    
por Otilia Domnea 24.05.2018 / 14:48

2 respostas

1

O comando sed no exemplo 6 extrai apenas o número de linhas da saída wc -l .

Ele está executando wc -l on $f (o arquivo que pertence à execução do script que foi passado como um argumento). Isso normalmente produziria uma saída assim:

$ wc -l .bashrc
17 .bashrc

Número de linhas na coluna 1 e nome do arquivo na coluna 2. O comando sed está capturando apenas o número de linhas de uma maneira bastante desnecessária.

$ wc -l .bashrc | sed 's/^\([0-9]*\).*$//'
17

A sed declaração 's/^\([0-9]*\).*$//' faz o seguinte:

  • ^ - corresponde ao início da linha
  • \([0-9]*\) - Corresponde a números ilimitados (Os parênteses com escape formam um grupo de captura)
  • .* - Corresponde a qualquer coisa vezes ilimitadas
  • $ - corresponde ao final da linha
  • - Representa o conteúdo do primeiro grupo de captura.

Essencialmente, isso corresponde a qualquer linha que comece com um número e substitua toda a linha pelo primeiro grupo de captura (o número).

Obrigado Stephen Kitt por recomendar isso:

$ wc -l < .bashrc
17

Caso contrário, usar cut ou awk seria melhor para algo assim:

$ wc -l .bashrc | cut -d' ' -f1
17

$ wc -l .bashrc | awk '{print $1}'
17
    
por 24.05.2018 / 15:26
1

O uso de sed nesse pedaço de código é analisar a saída de wc -l para extrair o número de linhas no arquivo.

Isso geralmente não é necessário, pois

l=$( wc -l <"$f" )

teria feito a mesma coisa (você deveria tentar isso).

O script usa algumas construções que não são portáteis e são consideradas "obsoletas", e há detalhes no script que o tornam inseguro.

  1. As expansões devem ser citadas. Por exemplo, if [ $# -lt 1 ] é melhor escrito como if [ "$#" -eq 0 ] e if [ -O $f ] deve ser if [ -O "$f" ] . Dessa forma, podemos suportar nomes de arquivos que contenham quaisquer caracteres, até mesmo caracteres que façam parte de $IFS (espaços, tabulações e novas linhas). O $# deve ser citado no caso de $IFS conter dígitos por algum motivo ou outro.

    Para mais informações, veja as três outras questões intituladas " Implicações de segurança de esquecer de citar uma variável em shells bash / POSIX "," Por que meu script de shell sufoca em espaços em branco ou outros caracteres especiais? "e" Quando é necessário citar duaspas? ".

  2. A substituição de comandos usando backticks é problemática em algumas circunstâncias. A linha que diz l='wc -l ...' pode ser reescrita como l=$(wc -l ...) . O mais recente $(...) é melhor, pois se encaixa, já que as cotações funcionam como o esperado (compare, por exemplo, echo "'echo "'echo ok'"'" , que gera um erro de sintaxe com echo "$(echo "$(echo ok)")" ) e é mais fácil de ler.

    Para mais informações, veja por exemplo " Os backshks (ou seja, 'cmd') em shells * sh foram preteridos? "

  3. $[ $s + $l ] é uma maneira não portátil de dizer $(( s + l )) .

  4. Os dados variáveis devem ser gerados usando printf em vez de usar echo . Por exemplo, essa última linha,

    echo "$n files in total, with $s lines in total"
    

    pode ser reescrito como

    printf '%d files in total, with %d lines in total\n' "$n" "$s"
    

    Ver, por exemplo, " Por que printf é melhor que echo? ".

  5. Usar $* para fazer loop sobre os argumentos da linha de comando impede que o script funcione em nomes de arquivos que contêm espaços.

  6. A instrução continue e a ramificação else da instrução if não são necessárias, pois aparecem no loop de qualquer maneira.

  7. A saída de diagnóstico deve ser impressa para erro padrão.

Versão "corrigida" do script:

#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of

if [ "$#" -eq 0 ]; then
    printf 'Usage: %s file ...\n' "$0" >&2
    exit 1
fi

printf '%s counts the lines of code\n' "$0"
l=0; n=0; s=0
for name do
    if [ -f "$name" ] && [ -O "$name" ]; then # checks whether its a regular file and file owner is running the script
        nlines=$( wc -l <"$name" )
        printf '%s: %d\n' "$name" "$nlines"
        totlines=$(( totlines + nlines ))
        nfiles=$(( nfiles + 1 ))
    fi
done

printf '%d files in total, with %s lines in total" "$nfiles" "$totlines"
    
por 24.05.2018 / 18:15