Número de caracteres na saída de um comando shell

10

Estou escrevendo um script que precisa calcular o número de caracteres na saída de um comando em uma única etapa .

Por exemplo, usar o comando readlink -f /etc/fstab deve retornar 10 porque a saída desse comando tem 10 caracteres.

Isso já é possível com variáveis armazenadas usando o seguinte código:

variable="somestring";
echo ${#variable};
# 10

Infelizmente, usar a mesma fórmula com uma string gerada por comando não funciona:

${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution

Eu entendo que é possível fazer isso salvando primeiro a saída em uma variável:

variable=$(readlink -f /etc/fstab);
echo ${#variable};

Mas gostaria de remover o passo extra.

Isso é possível? É preferível a compatibilidade com o shell Almquist (sh) usando apenas utilitários embutidos ou padrão.

    
por user339676 11.10.2014 / 04:03

5 respostas

7

Com GNU expr :

$ expr length + "$(readlink -f /etc/fstab)"
10

O + existe um recurso especial do GNU expr para garantir que o próximo argumento seja tratado como uma string, mesmo que seja um operador expr , como match , length , + ...

O acima irá remover qualquer nova linha de saída. Para contornar:

$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10

O resultado foi subtraído para 2 porque a nova linha final de readlink e o caractere . que adicionamos.

Com a string Unicode, expr parece não funcionar, porque retorna o comprimento da string em bytes em vez da contagem de caracteres (veja line 654 )

$ LC_ALL=C.UTF-8 expr length ăaa
4

Então, você pode usar:

$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3

POSIXLY:

$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10

O espaço antes da substituição do comando impede que o comando seja travado com o início da string com - , então precisamos subtrair 3.

    
por 11.10.2014 / 04:15
6

Não sei como fazer isso com os recursos internos do shell ( o Gnouc é ), mas as ferramentas padrão podem ajudar:

  1. Você pode usar wc -m , o que conta os caracteres. Infelizmente, também conta a nova linha final, então você teria que se livrar disso primeiro:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
    
  2. Você pode usar, é claro, awk

    readlink -f /etc/fstab | awk '{print length($0)}'
    
  3. Ou Perl

    readlink -f /etc/fstab | perl -lne 'print length'
    
por 11.10.2014 / 04:16
4

Eu geralmente faço assim:

$ echo -n "$variable" | wc -m
10

Para fazer comandos, eu o adaptaria assim:

$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10

Essa abordagem é semelhante ao que você estava fazendo em suas duas etapas, exceto que estamos combinando-as em um único liner.

    
por 11.10.2014 / 04:29
1

Você pode chamar utilitários externos (veja outras respostas), mas eles tornarão seu script mais lento, e é difícil conseguir o encanamento certo.

Zsh

No zsh, você pode escrever ${#$(readlink -f /etc/fstab)} para obter o tamanho da substituição do comando. Note que este não é o tamanho da saída do comando, é o tamanho da saída sem nenhuma nova linha.

Se você quiser o tamanho exato da saída, imprima um caractere extra de não-nova linha no final e subtraia um.

$((${#$(readlink -f /etc/fstab; echo .)} - 1))

Se o que você quer é o payload na saída do comando, então você precisa subtrair dois aqui, porque a saída de readlink -f é o caminho canônico mais uma nova linha.

$((${#$(readlink -f /etc/fstab; echo .)} - 2))

Isso difere de ${#$(readlink -f /etc/fstab)} no caso raro, mas possível, em que o próprio caminho canônico termina em uma nova linha.

Para este exemplo específico, você não precisa de um utilitário externo, pois o zsh tem uma construção interna equivalente a readlink -f , através do modificador de histórico A .

echo /etc/fstab(:A)

Para obter o tamanho, use o modificador de histórico em uma expansão de parâmetro:

${#${:-/etc/fstab}:A}

Se você tiver o nome do arquivo em uma variável filename , isso seria ${#filename:A} .

Cascas ao estilo Bourne / POSIX

Nenhuma das camadas Bourne / POSIX puras (Bourne, ash, mksh, ksh93, bash, yash…) tem uma extensão similar que eu conheço. Se você precisar aplicar uma substituição de parâmetro à saída de uma substituição de comando ou aninhar substituições de parâmetro, use etapas sucessivas.

Você pode colocar o processamento em uma função, se quiser.

command_output_length_sans_trailing_newlines () {
  set -- "$("$@")"
  echo "${#1}"
}

ou

command_output_length () {
  set -- "$("$@"; echo .)"
  echo "$((${#1} - 1))"
}

mas geralmente não há benefício; exceto com ksh93, que faz com que uma bifurcação extra seja capaz de usar a saída da função, fazendo com que seu script fique mais lento, e raramente há qualquer benefício de legibilidade.

Mais uma vez, a saída de readlink -f é o caminho canônico mais uma nova linha; se você quiser o comprimento do caminho canônico, subtraia 2 em vez de 1 em command_output_length . Usar command_output_length_sans_trailing_newlines fornece o resultado correto somente quando o caminho canônico em si não termina em uma nova linha.

Bytes vs caracteres

${#…} deve ser o comprimento em caracteres, não em bytes, o que faz diferença em localidades multibyte. Versões razoavelmente atualizadas de ksh93, bash e zsh calculam o comprimento em caracteres de acordo com o valor de LC_CTYPE no momento em que a construção ${#…} é expandida. Muitas outras shells comuns realmente não suportam localidades multibyte: a partir do traço 0.5.7, mksh 46 e posh 0.12.3, ${#…} retorna o comprimento em bytes. Se você quiser o comprimento em caracteres de maneira confiável, use o utilitário wc :

$(readlink -f /etc/fstab | wc -m)

Contanto que $LC_CTYPE designe uma localidade válida, você pode ter certeza de que isso causará erros (em uma plataforma antiga ou restrita que não suporta códigos de idiomas multibyte) ou retornará o tamanho correto em caracteres. (Para Unicode, “comprimento em caracteres” significa o número de pontos de código - o número de glifos é outra história, devido a complicações como a combinação de caracteres.)

Se você quiser o comprimento em bytes, defina LC_CTYPE=C temporariamente ou use wc -c em vez de wc -m .

A contagem de bytes ou caracteres com wc inclui qualquer nova linha no final do comando. Se você quiser o comprimento do caminho canônico em bytes, é

$(($(readlink -f /etc/fstab | wc -c) - 1))

Para obtê-lo em caracteres, subtraia 2.

    
por 11.10.2014 / 16:25
0

Isso funciona em dash , mas exige que o var de destino esteja definitivamente vazio ou não definido. É por isso que isso é realmente dois comandos - eu esvazio explicitamente $l no primeiro:

l=;printf '%.slen is %d and result is %s\n' \
    "${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"

OUTPUT

len is 10 and result is /etc/fstab

Isso é tudo shell builtins - não incluindo o readlink é claro - mas avaliá-lo no shell atual dessa forma implica que você deve fazer a atribuição antes de obter o len, e é por isso que eu co-prezo o primeiro argumento em a string %.s format e apenas adicione-a novamente para o valor literal na cauda da lista de argumentos de printf .

com printf :

l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\n "$l"

OUTPUT

10:/etc/fstab

Você pode se aproximar da mesma coisa, mas ao invés da saída em uma variável no primeiro comando, você a obtém no stdout:

PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"

... que escreve ...

10:/etc/fstab

... para o descritor de arquivo 1 sem atribuir qualquer valor a qualquer vars no shell atual.

    
por 11.10.2014 / 10:26