Fondamentalmente è un problema di portabilità (e affidabilità).
Inizialmente, echo
non 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 perl
quando tra virgolette doppie, \t
in 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 bash
e zsh
ma ormai era troppo tardi.
Ora, quando un UNIX standard echo
riceve un argomento che contiene i due caratteri \
e t
, invece di emetterli, emette un carattere di tabulazione. E non appena vede \c
un 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 -e
per espandere le sequenze di escape e un'opzione -n
per non visualizzare la nuova riga finale. Alcune hanno un -E
per disabilitare le sequenze di escape, altre hanno -n
ma non -e
, e l'elenco delle sequenze di escape supportate da un'implementazione echo
non è necessariamente lo stesso di quello supportato da un'altra.
Sven Mascheck ha una bella pagina che mostra la portata del problema .
Nelle echo
implementazioni che supportano le opzioni, in genere non c'è supporto per a --
per contrassegnare la fine delle opzioni (il echo
builtin 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 echo
in molte shell.
In alcune shell come bash
¹ o ksh93
² o yash
( $ECHO_STYLE
variabile), il comportamento dipende anche da come è stata compilata la shell o dall'ambiente ( echo
il comportamento di GNU cambierà anche se $POSIXLY_CORRECT
è nell'ambiente e con la versione 4 , zsh
con la sua bsd_echo
opzione, alcune basate su pdksh con la loro posix
opzione o se sono chiamate as sh
o meno). Quindi due bash
echo
s, anche della stessa versione di bash
non hanno la garanzia di comportarsi allo stesso modo.
POSIX afferma: se il primo argomento è -n
o qualsiasi argomento contiene barre rovesciate, allora il comportamento non è specificato. bash
echo in tal senso non è POSIX in quanto, ad esempio, echo -e
non sta emettendo -e<newline>
come richiesto da POSIX. La specifica UNIX è più rigorosa, proibisce -n
e richiede l'espansione di alcune sequenze di escape, inclusa quella \c
per 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 $var
non contenga caratteri di barra rovesciata e non inizi con -
. La specifica POSIX in realtà ci dice di usare printf
invece in quel caso.
Quindi, ciò significa che non puoi usare echo
per 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 echo
per visualizzarlo.
Questo va bene:
echo >&2 Invalid file.
Questo non è:
echo >&2 "Invalid file: $file"
(Tuttavia funzionerà correttamente con alcune echo
implementazioni (non conformi a UNIX) come bash
's quando l' xpg_echo
opzione 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 yash
con ECHO_STYLE=raw
(con l'avvertenza che yash
le 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 $var
seguito 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 printf
le implementazioni. C'è un nucleo di funzionalità che è specificato da POSIX, ma poi ci sono un sacco di estensioni. Ad esempio, alcuni supportano a %q
per citare gli argomenti ma il modo in cui viene fatto varia da shell a shell, alcuni supportano \uxxxx
i 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 printf
e 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 echo
può 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 IFS
molte 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, printf
non è 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
).
bash
di .echo
Con bash
, in fase di esecuzione, ci sono due cose che controllano il comportamento di echo
(accanto enable -n echo
o ridefinendo echo
come una funzione o un alias): l' xpg_echo
bash
opzione e se bash
è in modalità posix. posix
La modalità può essere abilitata se bash
viene chiamata come sh
o se POSIXLY_CORRECT
è nell'ambiente o con l' posix
opzione:
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 come richiesto da UNIX:
$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A
Onora ancora -n
e -e
(e -E
):
$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%
Con xpg_echo
la 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, bash
non è ancora conforme a POSIX in quanto non produce output -e
in:
$ 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-default
e --enable-strict-posix-default
dello configure
script. 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 In realtà, non è vero, il /bin/bash
./bin/bash
che Oracle distribuisce con Solaris 11 (in un pacchetto opzionale) sembra essere compilato con --enable-xpg-echo-default
(non era il caso di Solaris 10).
ksh93
diecho
In ksh93
, echo
l'espansione o meno delle sequenze di escape e il riconoscimento delle opzioni dipendono dal contenuto delle variabili di ambiente $PATH
and/or $_AST_FEATURES
.
Se $PATH
contiene un componente che contiene /5bin
o /xpg
prima del /bin
componente /usr/bin
o allora si comporta come SysV/UNIX (espande le sequenze, non accetta opzioni). Se trova /ucb
o /bsd
prima o se $_AST_FEATURES
7 contiene UNIVERSE = ucb
, allora si comporta come BSD 3 ( -e
per abilitare l'espansione, riconosce -n
).
L'impostazione predefinita è dipendente dal sistema, BSD su Debian (vedere l'output builtin getconf; getconf UNIVERSE
nelle 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
Il riferimento a BSD per la gestione dell'opzione -e
è un po' fuorviante qui. La maggior parte di quei echo
comportamenti diversi e incompatibili sono stati tutti introdotti in AT&T:
\n
, \0ooo
, \c
in Programmer's Work Bench UNIX (basato su Unix V6), e il resto ( \b
, \r
...) in Unix System III Ref .-n
in Unix V7 (di Dennis Ritchie Ref )-e
in Unix V8 (di Dennis Ritchie Ref )-E
probabilmente è originario di bash
(CWRU/CWRU.chlog nella versione 1.13.5 menziona che Brian Fox lo aggiunse il 18-10-1992, GNU echo
lo copiò poco dopo in sh-utils-1.8 rilasciato 10 giorni dopo)Sebbene il supporto echo
incorporato di sh
BSD sia stato fornito -e
fin da quando si è iniziato a usare la shell Almquist nei primi anni '90, l' echo
utilità autonoma non lo supporta ancora oggi ( FreeBSDecho
non lo supporta ancora -e
, sebbene lo supporti -n
come Unix V7 (e anche \c
se solo alla fine dell'ultimo argomento)).
La gestione di -e
è stata aggiunta a ksh93
's echo
nell'universo BSD nella versione ksh93r rilasciata nel 2006 e può essere disabilitata in fase di compilazione .
A partire da coreutils 8.31 (e da questo commit ), GNU echo
espande le sequenze di escape per impostazione predefinita quando POSIXLY_CORRECT è nell'ambiente, per adattarle al comportamento del bash -o posix -O xpg_echo
builtin echo
(vedere il report di bug ).
echo
La maggior parte delle versioni di macOS ha ricevuto la certificazione UNIX da OpenGroup .
Il loro sh
builtin echo
è conforme in quanto bash
(una versione molto vecchia) è compilato con xpg_echo
abilitato per impostazione predefinita, ma la loro utilità autonoma echo
non lo è. env echo -n
non produce nulla invece di -n<newline>
, env echo '\n'
produce \n<newline>
invece di <newline><newline>
.
Si /bin/echo
tratta di quello di FreeBSD che sopprime l'output di nuova riga se il primo argomento è -n
o (dal 1995) se l'ultimo argomento termina con \c
, ma non supporta altre sequenze di barre rovesciate richieste da UNIX, nemmeno \\
.
echo
implementazioni che possono produrre dati arbitrari letteralmenteA rigor di termini, potresti anche contare FreeBSD/macOS /bin/echo
sopra (non la loro shell echo
integrata) 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 -E
e -n
(o possono essere configurate per) possono anche fare:
echo -nE "$var
"
Per l'equivalente di printf '%s\n' "$var"
.
_AST_FEATURES
e 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 getconf
builtin (abilitato con builtin getconf
o tramite l'invocazione command /opt/ast/bin/getconf
di ) è l'interfaccia perastgetconf()
Ad esempio, dovresti builtin getconf; getconf UNIVERSE = att
cambiare l' UNIVERSE
impostazione in att
(facendo sì echo
che si comporti come SysV, tra le altre cose). Dopo averlo fatto, noterai che la $_AST_FEATURES
variabile d'ambiente contiene UNIVERSE = att
.