Perché printf è meglio di eco?


547

Ho sentito che printfè meglio di echo. Ricordo solo un'istanza della mia esperienza in cui ho dovuto usare printfperché echonon ha funzionato per inserire del testo in alcuni programmi su RHEL 5.8 ma printfha funzionato. Ma a quanto pare, ci sono altre differenze e vorrei sapere quali sono e se ci sono casi specifici in cui usare l'uno contro l'altro.


Whab circa echo -e?
neverMind9

1
@ neverMind9 echo -enon funziona sh, solo in bash(per qualche motivo), e la finestra mobile, ad esempio, utilizza shper impostazione predefinita per i suoi RUNcomandi
Ciprian Tomoiagă

@ CiprianTomoiagă echo -eper me funziona in Android Terminal, che è essenzialmente sh.
neverMind9

Risposte:


756

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

Inizialmente, echonon accettava alcuna opzione e non espandeva nulla. Tutto ciò che stava facendo era presentare i suoi argomenti separati da un carattere spaziale e terminati da un carattere di nuova riga.

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

Hanno poi pensato più intensamente ma invece di aggiungere quella funzionalità alla shell (come perldove all'interno delle doppie virgolette, in \trealtà significa un carattere di tabulazione), l'hanno aggiunta a echo.

David Korn si rese conto dell'errore e introdusse una nuova forma di citazioni di shell: $'...'che fu successivamente copiata bashe zshma era ormai troppo tardi.

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

Altre shell / fornitori / versioni di Unix hanno scelto di farlo diversamente: hanno aggiunto -eun'opzione per espandere le sequenze di escape e -nun'opzione per non generare la nuova riga finale. Alcuni hanno l'obbligo -Edi disabilitare le sequenze di escape, altri invece hanno, -nma -el'elenco delle sequenze di escape supportate da echoun'implementazione non è necessariamente lo stesso di quello supportato da un'altra.

Sven Mascheck ha una bella pagina che mostra l'entità del problema .

Per questi echoimplementazioni che supportano le opzioni, non c'è generalmente alcun supporto di una --per segnare la fine delle opzioni (il echobuiltin di alcune conchiglie di non-Bourne-come fare, e zsh supporti -per questo però), così per esempio, è difficile uscita "-n"con echoin molte conchiglie.

Su 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 si $POSIXLY_CORRECTtrova nell'ambiente e con la versione 4 , zshcon la sua bsd_echoopzione, alcuni basati su pdksh con la loro posixopzione o se sono chiamati come sho meno). Quindi non è garantito che due bash echosecondi, anche della stessa versione, bashsi comportino allo stesso modo.

POSIX dice: se il primo argomento è -no qualsiasi argomento contiene barre rovesciate, il comportamento non è specificato . bashl'eco al riguardo non è POSIX in quanto, ad esempio, echo -enon viene emessa -e<newline>come POSIX richiede. La specifica UNIX è più rigorosa, proibisce -ne richiede l'espansione di alcune sequenze di escape tra cui \cquella per interrompere l'output.

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 alla ^-([eEn]*|-help|-version)$regexp estesa o qualsiasi argomento contiene barre rovesciate (o caratteri la cui codifica contiene la codifica del carattere barra rovesciata come αnelle localizzazioni che utilizzano il set di caratteri BIG5), 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.

Ciò significa che non è possibile utilizzare 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 usarlo echoper visualizzarlo.

Questo va bene:

echo >&2 Invalid file.

Questo non è:

echo >&2 "Invalid file: $file"

(Anche se funzionerà bene con alcune echoimplementazioni (non conformi a UNIX) come quelle bashquando l' xpg_echoopzione non è stata abilitata in un modo o nell'altro come al momento della compilazione o tramite l'ambiente).

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

printf, d'altra parte è più affidabile, almeno quando è limitato all'utilizzo di base di echo.

printf '%s\n' "$var"

Produrrà il contenuto $varseguito da un carattere di nuova riga indipendentemente dal personaggio che può contenere.

printf '%s' "$var"

Lo produrrà senza il carattere di nuova riga finale.

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

Ma alla fine, se ti attieni al set di funzionalità POSIX printfe non provi a fare qualcosa di troppo fantasioso, sei senza problemi.

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

Un più affidabile echopuò essere implementato usando 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 ulteriore processo nella maggior parte delle implementazioni della shell) può essere evitata usando local IFScon molte shell o scrivendola come:

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

Appunti

1. come bashil echocomportamento può essere modificato.

Con bash, in fase di esecuzione, ci sono due cose che controllano il comportamento di echo(accanto enable -n echoo ridefinendo echocome funzione o alias): l' xpg_echo bashopzione e se bashè in modalità posix. posixla modalità può essere abilitata se bashviene chiamata come sho se si POSIXLY_CORRECTtrova 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_echo espande le sequenze quando UNIX richiede:

$ 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_echoe 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. Si noti che in modalità POSIX, non bashè ancora conforme a POSIX in quanto non viene emesso -ein:

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

$

I valori predefiniti per xpg_echo e posix possono essere definiti al momento della compilazione con le opzioni --enable-xpg-echo-defaulte --enable-strict-posix-defaultper lo configurescript. Questo è in genere ciò che fanno le versioni recenti di OS / X per crearne una /bin/sh. Tuttavia, nessuna implementazione / distribuzione Unix / Linux nella loro mente corretta in genere lo farebbe/bin/bash . In realtà, questo non è vero, quello con /bin/bashcui Oracle viene fornito con Solaris 11 (in un pacchetto opzionale) sembra essere costruito con --enable-xpg-echo-default(non era il caso in Solaris 10).

2. Come ksh93s' echocomportamento può essere modificato.

In ksh93, se echoespande le sequenze di escape o meno e riconosce le opzioni dipende dal contenuto delle variabili di ambiente $PATHe / o $_AST_FEATURES.

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

L'impostazione predefinita dipende dal sistema, BSD su Debian (vedere l'output di builtin getconf; getconf UNIVERSEnelle ultime versioni 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 l'eco -e?

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

  • \n, \0ooo, \cIn panchina per il programmatore di lavoro 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 )
  • -Estesso probabilmente originariamente proveniva da bash(CWRU / CWRU.chlog nella versione 1.13.5 menziona Brian Fox aggiungendolo nel 1992-10-18, GNU echocopiandolo poco dopo in sh-utils-1.8 rilasciato 10 giorni dopo)

Mentre il echobuiltin del sho BSD ha supportato -edal giorno in cui hanno iniziato a utilizzare la shell Almquist per esso nei primi anni '90, l' echoutilità autonoma fino ad oggi non lo supporta lì ( FreeBSDecho non supporta ancora -e, sebbene supporti -ncome Unix V7 (e anche \cma solo alla fine dell'ultimo argomento)).

La gestione di -estato aggiunto al ksh93's echoquando nella BSD nell'universo nella versione ksh93r uscito nel 2006 e può essere disabilitato in fase di compilazione.

4. GNU fa eco al cambiamento di comportamento in 8.31

Dal momento che coreutils 8.31 (e questo commettono ), GNU echoora si espande sequenze di escape per impostazione predefinita quando POSIXLY_CORRECT è nell'ambiente, per abbinare il comportamento di bash -o posix -O xpg_echo's echoincorporato (vedi bug report ).

5. macOS echo

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

Il loro shbuilt-in echoè conforme in quanto è bash(una versione molto vecchia) costruito con xpg_echoabilitato per impostazione predefinita, ma la loro echoutilità autonoma non lo è. env echo -nnon genera nulla invece di -n<newline>, env echo '\n'genera \n<newline>invece di <newline><newline>.

Questo /bin/echoè quello di FreeBSD che sopprime l'output newline se il primo argomento è -no (dal 1995) se l'ultimo argomento finisce \c, ma non supporta altre sequenze di barra rovesciata richieste da UNIX, nemmeno \\.

6. echoimplementazioni in grado di produrre dati arbitrari alla lettera

A rigor di termini, puoi anche contare che FreeBSD / macOS /bin/echosopra (non il echobuiltin della loro shell ) dove si potrebbe scrivere zsh' echo -E - "$var"o yash' ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var")

/bin/echo "$var
\c"

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

echo -nE "$var
"

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

/bin/echo "$var\c"

7. _AST_FEATURESe ASTUNIVERSE

Non _AST_FEATURESè destinato a essere manipolato direttamente, ma viene utilizzato per propagare le impostazioni di configurazione AST attraverso l'esecuzione del comando. La configurazione deve essere eseguita tramite l' astgetconf()API (non documentata) . All'interno ksh93, il getconfbuiltin (abilitato con builtin getconfo tramite invocazione command /opt/ast/bin/getconf) è l'interfaccia aastgetconf()

Ad esempio, dovresti builtin getconf; getconf UNIVERSE = attmodificare l' UNIVERSEimpostazione su att(causando il echocomportamento SysV tra le altre cose). Dopo averlo fatto, noterai che la $_AST_FEATURESvariabile di ambiente contiene UNIVERSE = att.


13
Molti dei primi sviluppi di Unix sono avvenuti in isolamento e non sono stati applicati buoni principi di ingegneria del software come "quando si cambia l'interfaccia, si cambia il nome" .
Henk Langeveld,

7
Come nota, l'unico (e forse unico) vantaggio di avere l' echoespansione delle \xsequenze rispetto alla shell come parte della sintassi di quotazione è che è quindi possibile emettere un byte NUL (un altro design probabilmente discutibile di Unix è quei delimitati da null stringhe, dove metà del sistema chiama (come execve()) non può accettare sequenze arbitrarie di byte)
Stéphane Chazelas

Come si può vedere dalla pagina Web di Sven Maschek, printfsembra anche essere un problema poiché la maggior parte delle implementazioni lo fanno male. L'unica corretta implementazione di cui sono a conoscenza è in boshe la pagina di Sven Maschek non elenca i problemi con byte null via \0.
schily

1
Questa è un'ottima risposta - grazie @ StéphaneChazelas per averlo scritto.
JoshuaRLi,

28

Potresti voler usare printfper le sue opzioni di formattazione. echoè utile quando si tratta di stampare il valore di una variabile o di una (semplice) linea, ma è tutto quello che c'è da fare. printffondamentalmente può fare ciò che può fare la versione C.

Esempio di utilizzo e funzionalità:

Echo:

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf:

vech="bike"
printf "%s\n" "$vech"

fonti:


@ 0xC0000022L Sono corretto grazie. Non ho notato che mi sono collegato al sito sbagliato nella mia fretta per rispondere alla domanda. Grazie per il tuo contributo e la correzione.
NlightNFotis,

7
L'uso echoper stampare una variabile può fallire se il valore della variabile contiene meta-caratteri.
Keith Thompson,

17

Un "vantaggio", se vuoi chiamarlo così, sarebbe che non devi dirlo come echointerpretare certe sequenze di escape come \n. Sa interpretarli e non richiede -edi farlo.

printf "some\nmulti-lined\ntext\n"

(NB: l'ultimo \nè necessario, lo echoimplica, a meno che tu non dia l' -nopzione)

contro

echo -e "some\nmulti-lined\ntext"

Nota l'ultimo \nin printf. Alla fine della giornata è una questione di gusti e requisiti ciò che usi: echoo printf.


1
Vero per /usr/bin/echoe bashincorporato. Il dash, kshe zshbuiltin echonon ha bisogno di -einterruttore per espandere i caratteri con escape backslash.
arte

Perché le citazioni di paura? La tua formulazione implica che non è necessariamente un vero vantaggio.
Keith Thompson,

2
@KeithThompson: in realtà tutto ciò che dovrebbero implicare è che non tutti possono considerarlo un vantaggio.
0xC0000022L

Potrebbe approfondire che? Perché non sarebbe un vantaggio? La frase "se vuoi chiamarlo così" implica piuttosto fortemente che pensi che non lo sia.
Keith Thompson,

2
O potresti semplicemente fare printf '%s\n' 'foo\bar.
nyuszika7h

6

Uno svantaggio printfè rappresentato dalle prestazioni perché la shell integrata echoè molto più veloce. Questo entra in gioco in particolare in Cygwin, dove ogni istanza di un nuovo comando provoca un pesante sovraccarico di Windows. Quando ho cambiato il mio programma /bin/echopieno di eco dall'uso all'eco della shell, le prestazioni sono quasi raddoppiate. È un compromesso tra portabilità e prestazioni. Non è una schiacciata da usare sempre printf.


15
printfè costruito nella maggior parte delle shell al giorno d'oggi (bash, dash, ksh, zsh, yash, alcuni derivati ​​pdksh ... quindi include anche le shell che si trovano tipicamente su cygwin). Le uniche eccezioni degne di nota sono alcuni pdkshderivati.
Stéphane Chazelas,

Ma molte di quelle implementazioni di printf sono rotte. Questo è essenziale quando ti piace usare printfper generare nul byte, ma alcune implementazioni interpretano `\ c 'nella stringa di formato anche se non dovrebbero.
schily,

2
@schily, il comportamento di printfPOSIX non è specificato se lo si utilizza \cnella stringa di formato, quindi le printfimplementazioni possono fare tutto ciò che vogliono al riguardo. Ad esempio, alcuni lo trattano come nel PWB echo(causa l'uscita di printf), ksh lo usa per \cAi caratteri di controllo (per l'argomento del formato printf e per $'...'ma non per echoprint!). Non sei sicuro di cosa abbia a che fare con la stampa di byte NUL o forse ti riferisci a ksh's printf '\c@'?
Stéphane Chazelas,
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.