Numero di caratteri nell'output di un comando shell


12

Sto scrivendo uno script che deve calcolare il numero di caratteri nell'output di un comando in un unico passaggio .

Ad esempio, l'utilizzo del comando readlink -f /etc/fstabdovrebbe restituire 10perché l'output di quel comando è lungo 10 caratteri.

Questo è già possibile con le variabili memorizzate utilizzando il seguente codice:

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

Sfortunatamente, l'utilizzo della stessa formula con una stringa generata dal comando non funziona:

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

Capisco che è possibile farlo salvando prima l'output in una variabile:

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

Ma vorrei rimuovere il passaggio aggiuntivo.

È possibile? È preferibile la compatibilità con la shell Almquist (sh) utilizzando solo utility integrate o standard.


1
L'output di readlink -f /etc/fstabè di 11 caratteri. Non dimenticare la nuova riga. Altrimenti vedresti /etc/fstabluser@cern:~$ quando l'hai eseguito da una shell.
Phil Frost,

@PhilFrost sembra che tu abbia un messaggio divertente, lavori al CERN?
Dmitry Grigoryev,

Risposte:


9

Con GNU expr :

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

Il +là è una caratteristica speciale di GNU exprper assicurarsi che il prossimo argomento viene trattato come una stringa, anche se sembra essere un exproperatore come match, length, +...

Quanto sopra eliminerà qualsiasi nuova riga finale di output. Per aggirare il problema:

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

Il risultato è stato sottratto a 2 perché la nuova riga finale readlinke il personaggio che .abbiamo aggiunto.

Con la stringa Unicode, exprsembra non funzionare, perché restituisce la lunghezza della stringa in byte anziché il conteggio dei caratteri (Vedi riga 654 )

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

Quindi, puoi usare:

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

POSIXLY:

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

Lo spazio prima della sostituzione del comando impedisce che il comando venga arrestato in modo anomalo con la stringa che inizia con -, quindi è necessario sottrarre 3.


Grazie! Sembra che il tuo terzo esempio LC_ALL=C.UTF-8funzioni anche senza il , il che semplifica notevolmente le cose se la codifica della stringa non sarà conosciuta in anticipo.
user339676,

2
expr length $(echo "*")- no. Almeno utilizzare le virgolette doppie: expr length "$(…)". Ma questo elimina le nuove righe finali dal comando, è una caratteristica inevitabile della sostituzione dei comandi. (Puoi aggirare il problema, ma poi la risposta diventa ancora più complessa.)
Gilles 'SO- smetti di essere malvagio'

6

Non sono sicuro di come farlo con i builtin della shell ( Gnouc è però ) ma gli strumenti standard possono aiutare:

  1. Puoi usare i wc -mcaratteri che contano. Sfortunatamente, conta anche la nuova riga finale, quindi dovresti sbarazzartene prima:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. Ovviamente puoi usare awk

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

    readlink -f /etc/fstab | perl -lne 'print length'

Vuoi dire che exprè un built-in? In quale shell?
Mikeserv,

5

Di solito lo faccio in questo modo:

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

Per eseguire i comandi, lo adatterò in questo modo:

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

Questo approccio è simile a quello che stavi facendo nei tuoi 2 passaggi, tranne per il fatto che li stiamo combinando in un unico liner.


2
È necessario utilizzare -minvece di -c. Con i caratteri Unicode, il tuo approccio verrà interrotto.
cuonglm,

1
Perché non semplicemente readlink -f /etc/fstab | wc -m?
Phil Frost,

1
Perché usi questo metodo inaffidabile invece di ${#variable}? Almeno usa le virgolette doppie echo -n "$variable", ma questo non riesce ancora se, ad esempio, il valore di variableè -e. Quando lo usi in combinazione con una sostituzione di comando, tieni presente che le nuove righe finali vengono rimosse.
Gilles 'SO- smetti di essere malvagio' l'

@philfrost b / c quello che ho mostrato costruito a partire da quello che stava già pensando. Funziona anche con tutti i cmd che può avere impostato prima in vari modi e desidera le loro lunghezze dopo le parole. Anche Terdon ha già questo esempio.
slm

1

Puoi chiamare utility esterne (vedi altre risposte), ma rallenteranno il tuo script ed è difficile trovare il giusto impianto idraulico.

zsh

In zsh, puoi scrivere ${#$(readlink -f /etc/fstab)}per ottenere la lunghezza della sostituzione del comando. Si noti che questa non è la lunghezza dell'output del comando, è la lunghezza dell'output senza alcuna nuova riga finale.

Se si desidera la lunghezza esatta dell'output, emettere un carattere extra non newline alla fine e sottrarre uno.

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

Se quello che vuoi è il payload nell'output del comando, allora devi sottrarre due qui, perché l'output di readlink -fè il percorso canonico più una nuova riga.

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

Ciò differisce dal ${#$(readlink -f /etc/fstab)}raro ma possibile caso in cui il percorso canonico stesso termina in una nuova riga.

Per questo esempio specifico, non hai bisogno di alcuna utility esterna, perché zsh ha un costrutto integrato che è equivalente a readlink -f, tramite il modificatore di cronologia A.

echo /etc/fstab(:A)

Per ottenere la lunghezza, utilizzare il modificatore della cronologia in un'espansione dei parametri:

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

Se hai il nome del file in una variabile filename, sarebbe ${#filename:A}.

Conchiglie tipo Bourne / POSIX

Nessuna delle shell Bourne / POSIX pure (Bourne, ash, mksh, ksh93, bash, yash ...) ha un'estensione simile che io conosca. Se è necessario applicare una sostituzione di parametri all'output di una sostituzione di comando o nidificare sostituzioni di parametri, utilizzare le fasi successive.

Se lo desideri, puoi inserire l'elaborazione in una funzione.

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

o

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

ma di solito non ci sono benefici; eccetto con ksh93, questo fa sì che un fork extra sia in grado di usare l'output della funzione, quindi rallenta lo script e raramente ci sono vantaggi di leggibilità.

Ancora una volta, l'output di readlink -fè il percorso canonico più una nuova riga; se vuoi la lunghezza del percorso canonico, sottrai 2 invece di 1 pollice command_output_length. L'utilizzo command_output_length_sans_trailing_newlinesdà il risultato giusto solo quando il percorso canonico stesso non termina in una nuova riga.

Byte vs caratteri

${#…}dovrebbe essere la lunghezza in caratteri, non in byte, il che fa la differenza nei locali multibyte. Versioni ragionevolmente aggiornate di ksh93, bash e zsh calcolano la lunghezza in caratteri in base al valore LC_CTYPEal momento dell'espansione del ${#…}costrutto. Molte altre shell comuni non supportano realmente le localizzazioni multibyte: dal trattino 0.5.7, mksh 46 e posh 0.12.3, ${#…}restituisce la lunghezza in byte. Se si desidera la lunghezza in caratteri in modo affidabile, utilizzare l' wcutilità:

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

Fintanto che $LC_CTYPEdesigna una locale valida, puoi essere sicuro che questo si risolverà in un errore (su una piattaforma antica o limitata che non supporta le impostazioni locali multibyte) o restituirà la lunghezza corretta in caratteri. (Per Unicode, "lunghezza in caratteri" indica il numero di punti di codice - il numero di glifi è ancora un'altra storia, a causa di complicazioni come la combinazione di caratteri.)

Se si desidera la lunghezza in byte, impostare LC_CTYPE=Ctemporaneamente o utilizzare wc -cinvece di wc -m.

Il conteggio di byte o caratteri wcinclude tutte le nuove righe finali del comando. Se vuoi la lunghezza del percorso canonico in byte, lo è

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

Per ottenere in caratteri, sottrarre 2.


@cuonglm No, devi sottrarre 1. echo .aggiunge due caratteri, ma il secondo carattere è una nuova riga finale che viene rimossa dalla sostituzione del comando.
Gilles 'SO- smetti di essere malvagio' il

La nuova riga proviene readlinkdall'output, più il .da echo. Concordiamo entrambi che echo .aggiungere due personaggi ma la nuova riga finale è stata eliminata. Prova con printf .o vedi la mia risposta unix.stackexchange.com/a/160499/38906 .
cuonglm,

@cuonglm La domanda ha posto il numero di caratteri nell'output del comando. L'output di readlinkè la destinazione del collegamento più una nuova riga.
Gilles 'SO- smetti di essere malvagio' il

0

Questo funziona dashma richiede che il var target sia definitivamente vuoto o non impostato. Questo è il motivo per cui si tratta in realtà di due comandi: svuoto esplicitamente $lnel primo:

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

PRODUZIONE

len is 10 and result is /etc/fstab

Sono tutti builtin della shell - non readlinkovviamente compreso - ma valutarlo nella shell corrente in questo modo implica che devi fare il compito prima di ottenere la len, motivo per cui %.silence il primo argomento nella printfstringa di formato e lo aggiungo di nuovo per il valore letterale alla fine printfdell'elenco arg.

Con eval:

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

PRODUZIONE

10:/etc/fstab

Puoi avvicinarti alla stessa cosa, ma invece dell'output in una variabile nel primo comando lo ottieni su stdout:

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

... che scrive ...

10:/etc/fstab

... al descrittore di file 1 senza assegnare alcun valore a nessuna variabile nella shell corrente.


1
Non è esattamente quello che l'OP voleva evitare? "Capisco che è possibile farlo salvando prima l'output in una variabile: variable=$(readlink -f /etc/fstab); echo ${#variable};Ma vorrei rimuovere il passaggio aggiuntivo."
terdon

@terdon, probabilmente ho capito male, ma ho avuto l'impressione che il punto e virgola fosse il problema e non la variabile. Ecco perché questi ottengono la len e l'output in un singolo semplice comando usando solo i comandi incorporati della shell. La shell non esegue readlink quindi exec expr, per esempio. Probabilmente importa solo se in qualche modo ottenere la len occlude il valore, il che ammetto che sto avendo difficoltà a capire perché potrebbe essere, ma sospetto che potrebbe esserci un caso in cui è importante.
Mikeserv,

1
Il evalmodo, a proposito, è probabilmente il più pulito qui - assegna l'output e la len allo stesso nome var in una singola esecuzione - molto vicino a fare l=length(l):out(l). A proposito expr length $(command) , occlude il valore a favore della len.
Mikeserv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.