Perché printf è migliore di echo?

Fondamentalmente è un problema di portabilità (e affidabilità).

Inizialmente, echonon accettava alcuna opzione e non espandeva nulla. Tutto ciò che faceva era inviare in output i suoi argomenti separati da uno spazio e terminati da un carattere di nuova riga.

Ora, qualcuno ha pensato che sarebbe stato bello se potessimo fare cose come echo "\n\t"visualizzare caratteri di nuova riga o di tabulazione, oppure avere un'opzione per non visualizzare il carattere di nuova riga finale.

Ci hanno pensato su più approfonditamente, ma invece di aggiungere quella funzionalità alla shell (come perlquando tra virgolette doppie, \tin realtà significa un carattere di tabulazione), l'hanno aggiunta a echo.

David Korn si rese conto dell'errore e introdusse una nuova forma di virgolette semplici: $'...'che fu poi copiata da bashe zshma ormai era troppo tardi.

Ora, quando un UNIX standard echoriceve un argomento che contiene i due caratteri \e t, invece di emetterli, emette un carattere di tabulazione. E non appena vede \cun argomento, smette di emettere (quindi la nuova riga finale non viene emessa).

Altre shell/fornitori/versioni Unix hanno scelto di farlo diversamente: hanno aggiunto un'opzione -eper espandere le sequenze di escape e un'opzione -nper non visualizzare la nuova riga finale. Alcune hanno un -Eper disabilitare le sequenze di escape, altre hanno -nma non -e, e l'elenco delle sequenze di escape supportate da un'implementazione echonon è necessariamente lo stesso di quello supportato da un'altra.

Sven Mascheck ha una bella pagina che mostra la portata del problema .

Nelle echoimplementazioni che supportano le opzioni, in genere non c'è supporto per a --per contrassegnare la fine delle opzioni (il echobuiltin di alcune shell non di tipo Bourne e toybox echo(l'utilità autonoma di Android echo) lo fa, e zsh lo supporta -), quindi, ad esempio, è difficile eseguire l'output "-n"con echoin molte shell.

In alcune shell come bash¹ o ksh93² o yash( $ECHO_STYLEvariabile), il comportamento dipende anche da come è stata compilata la shell o dall'ambiente ( echoil comportamento di GNU cambierà anche se $POSIXLY_CORRECTè nell'ambiente e con la versione 4 , zshcon la sua bsd_echoopzione, alcune basate su pdksh con la loro posixopzione o se sono chiamate as sho meno). Quindi due bash echos, anche della stessa versione di bashnon hanno la garanzia di comportarsi allo stesso modo.

POSIX afferma: se il primo argomento è -no qualsiasi argomento contiene barre rovesciate, allora il comportamento non è specificato. bashecho in tal senso non è POSIX in quanto, ad esempio, echo -enon sta emettendo -e<newline>come richiesto da POSIX. La specifica UNIX è più rigorosa, proibisce -ne richiede l'espansione di alcune sequenze di escape, inclusa quella \cper interrompere l'emissione.

Queste specifiche non vengono davvero in soccorso qui, dato che molte implementazioni non sono conformi. Anche alcuni sistemi certificati come macOS 5 non sono conformi.

Per rappresentare realmente la realtà attuale, POSIX dovrebbe effettivamente dire : se il primo argomento corrisponde all'espressione ^-([eEn]*|-|-help|-version)$regolare estesa o un argomento qualsiasi contiene barre rovesciate (o caratteri la cui codifica contiene la codifica del carattere barra rovesciata, come αnelle impostazioni locali che utilizzano il set di caratteri BIG5), allora il comportamento non è specificato.

Tutto sommato, non sai cosa echo "$var"verrà prodotto a meno che tu non possa assicurarti che $varnon contenga caratteri di barra rovesciata e non inizi con -. La specifica POSIX in realtà ci dice di usare printfinvece in quel caso.

Quindi, ciò significa che non puoi usare echoper visualizzare dati non controllati. In altre parole, se stai scrivendo uno script e sta prendendo input esterno (dall'utente come argomenti, o nomi di file dal file system...), non puoi usare echoper visualizzarlo.

Questo va bene:

echo >&2 Invalid file.

Questo non è:

echo >&2 "Invalid file: $file"

(Tuttavia funzionerà correttamente con alcune echoimplementazioni (non conformi a UNIX) come bash's quando l' xpg_echoopzione non è stata abilitata in un modo o nell'altro, ad esempio in fase di compilazione o tramite l'ambiente).

file=$(echo "$var" | tr ' ' _)non è accettabile nella maggior parte delle implementazioni (le eccezioni sono yashcon ECHO_STYLE=raw(con l'avvertenza che yashle variabili di 's non possono contenere sequenze arbitrarie di byte, quindi non nomi di file arbitrari) e zsh's echo -E - "$var"6 ).

printf, d'altro canto, è più affidabile, almeno quando è limitato all'uso di base di echo.

printf '%s\n' "$var"

Verrà visualizzato il contenuto $varseguito da un carattere di nuova riga, indipendentemente dal carattere che potrebbe contenere.

printf '%s' "$var"

Verrà visualizzato senza il carattere di nuova riga finale.

Ora, ci sono anche differenze tra printfle implementazioni. C'è un nucleo di funzionalità che è specificato da POSIX, ma poi ci sono un sacco di estensioni. Ad esempio, alcuni supportano a %qper citare gli argomenti ma il modo in cui viene fatto varia da shell a shell, alcuni supportano \uxxxxi caratteri Unicode. Il comportamento varia per printf '%10s\n' "$var"nelle impostazioni locali multi-byte, ci sono almeno tre risultati diversi perprintf %b '\123'

Ma alla fine, se ci si attiene alle funzionalità POSIX printfe non si cerca di fare nulla di troppo elaborato, si è fuori dai guai.

Ma ricorda che il primo argomento è il formato, quindi non dovrebbe contenere dati variabili/non controllati.

Un metodo più affidabile echopuò essere implementato utilizzando printf, come:

echo() ( # subshell for local scope for $IFS
  IFS=" " # needed for "$*"
  printf '%s\n' "$*"
)

echo_n() (
  IFS=" "
  printf %s "$*"
)

echo_e() (
  IFS=" "
  printf '%b\n' "$*"
)

La subshell (che implica la generazione di un processo extra nella maggior parte delle implementazioni shell) può essere evitata utilizzando local IFSmolte shell oppure scrivendola in questo modo:

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
     if [ "$#" -gt 0 ]; then
       printf ' %s' "$@"
     fi
  fi
  printf '\n'
}

In ksh88 e pdksh e alcuni dei suoi derivati, printfnon è incorporato. Lì, potresti preferire usare print -r --(for echo) e print -rn --(for echo -n/ \c) che stampano i loro argomenti separati da spazi (e seguiti da una nuova riga senza -n) senza alterazione (funziona anche in zsh).


Appunti

1. Come può essere modificato il comportamento bashdi .echo

Con bash, in fase di esecuzione, ci sono due cose che controllano il comportamento di echo(accanto enable -n echoo ridefinendo echocome una funzione o un alias): l' xpg_echo bashopzione e se bashè in modalità posix. posixLa modalità può essere abilitata se bashviene chiamata come sho se POSIXLY_CORRECTè nell'ambiente o con l' posixopzione:

Comportamento predefinito sulla maggior parte dei sistemi:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echoespande le sequenze come richiesto da UNIX:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

Onora ancora -ne -e(e -E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

Con xpg_echola modalità POSIX:

$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A

Questa volta, bashè conforme sia a POSIX che a UNIX. Nota che in modalità POSIX, bashnon è ancora conforme a POSIX in quanto non produce output -ein:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

I valori predefiniti per xpg_echo e posix possono essere definiti in fase di compilazione con le opzioni --enable-xpg-echo-defaulte --enable-strict-posix-defaultdello configurescript. Questo è in genere ciò che fanno le versioni recenti di OS/X per compilare il loro /bin/sh. Nessuna implementazione/distribuzione Unix/Linux sana di mente lo farebbe in genere per /bin/bash. In realtà, non è vero, il /bin/bashche Oracle distribuisce con Solaris 11 (in un pacchetto opzionale) sembra essere compilato con --enable-xpg-echo-default(non era il caso di Solaris 10).

2. Come può essere alterato il comportamento ksh93diecho

In ksh93, echol'espansione o meno delle sequenze di escape e il riconoscimento delle opzioni dipendono dal contenuto delle variabili di ambiente $PATHand/or $_AST_FEATURES.

Se $PATHcontiene un componente che contiene /5bino /xpgprima del /bincomponente /usr/bino allora si comporta come SysV/UNIX (espande le sequenze, non accetta opzioni). Se trova /ucbo /bsdprima o se $_AST_FEATURES7 contiene UNIVERSE = ucb, allora si comporta come BSD 3 ( -eper abilitare l'espansione, riconosce -n).

L'impostazione predefinita è dipendente dal sistema, BSD su Debian (vedere l'output builtin getconf; getconf UNIVERSEnelle versioni recenti di ksh93):

$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD

3. BSD per echo -e?

Il riferimento a BSD per la gestione dell'opzione -eè un po' fuorviante qui. La maggior parte di quei echocomportamenti diversi e incompatibili sono stati tutti introdotti in AT&T:

  • \n, \0ooo, \cin Programmer's Work Bench UNIX (basato su Unix V6), e il resto ( \b, \r...) in Unix System III Ref .
  • -nin Unix V7 (di Dennis Ritchie Ref )
  • -ein Unix V8 (di Dennis Ritchie Ref )
  • -Eprobabilmente è originario di bash(CWRU/CWRU.chlog nella versione 1.13.5 menziona che Brian Fox lo aggiunse il 18-10-1992, GNU echolo copiò poco dopo in sh-utils-1.8 rilasciato 10 giorni dopo)

Sebbene il supporto echoincorporato di shBSD sia stato fornito -efin da quando si è iniziato a usare la shell Almquist nei primi anni '90, l' echoutilità autonoma non lo supporta ancora oggi ( FreeBSDecho non lo supporta ancora -e, sebbene lo supporti -ncome Unix V7 (e anche \cse solo alla fine dell'ultimo argomento)).

La gestione di -eè stata aggiunta a ksh93's echonell'universo BSD nella versione ksh93r rilasciata nel 2006 e può essere disabilitata in fase di compilazione .

4. GNU echo cambia comportamento in 8.31

A partire da coreutils 8.31 (e da questo commit ), GNU echoespande le sequenze di escape per impostazione predefinita quando POSIXLY_CORRECT è nell'ambiente, per adattarle al comportamento del bash -o posix -O xpg_echobuiltin echo(vedere il report di bug ).

5. Sistema operativo MacOSecho

La maggior parte delle versioni di macOS ha ricevuto la certificazione UNIX da OpenGroup .

Il loro shbuiltin echoè conforme in quanto bash(una versione molto vecchia) è compilato con xpg_echoabilitato per impostazione predefinita, ma la loro utilità autonoma echonon lo è. env echo -nnon produce nulla invece di -n<newline>, env echo '\n'produce \n<newline>invece di <newline><newline>.

Si /bin/echotratta di quello di FreeBSD che sopprime l'output di nuova riga se il primo argomento è -no (dal 1995) se l'ultimo argomento termina con \c, ma non supporta altre sequenze di barre rovesciate richieste da UNIX, nemmeno \\.

6. echoimplementazioni che possono produrre dati arbitrari letteralmente

A rigor di termini, potresti anche contare FreeBSD/macOS /bin/echosopra (non la loro shell echointegrata) dove zsh's echo -E - "$var"o yash's ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") potrebbero essere scritti:

/bin/echo "$var
\c"

E zsh's echo -nE - "$var"( printf %s "$var") potrebbe essere scritto

/bin/echo "$var\c"

Le implementazioni che supportano -Ee -n(o possono essere configurate per) possono anche fare:

echo -nE "$var
"

Per l'equivalente di printf '%s\n' "$var".

7. _AST_FEATURESe l'ASTUNIVERSE

Non _AST_FEATURESè pensato per essere manipolato direttamente, è usato per propagare le impostazioni di configurazione AST attraverso l'esecuzione dei comandi. La configurazione è pensata per essere eseguita tramite l' astgetconf()API (non documentata). All'interno di ksh93, il getconfbuiltin (abilitato con builtin getconfo tramite l'invocazione command /opt/ast/bin/getconfdi ) è l'interfaccia perastgetconf()

Ad esempio, dovresti builtin getconf; getconf UNIVERSE = attcambiare l' UNIVERSEimpostazione in att(facendo sì echoche si comporti come SysV, tra le altre cose). Dopo averlo fatto, noterai che la $_AST_FEATURESvariabile d'ambiente contiene UNIVERSE = att.

Ultimo aggiornamento 19.10.2024 08:08:30
index FAQ