Fondamentalmente, è un problema di portabilità (e affidabilità).
Inizialmente, echo
non 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 perl
dove all'interno delle doppie virgolette, in \t
realtà 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 bash
e zsh
ma era ormai troppo tardi.
Ora quando un UNIX standard echo
riceve un argomento che contiene i due caratteri \
e t
, invece di emetterli, genera un carattere di tabulazione. E non appena vede \c
in 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 -e
un'opzione per espandere le sequenze di escape e -n
un'opzione per non generare la nuova riga finale. Alcuni hanno l'obbligo -E
di disabilitare le sequenze di escape, altri invece hanno, -n
ma -e
l'elenco delle sequenze di escape supportate da echo
un'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 echo
implementazioni che supportano le opzioni, non c'è generalmente alcun supporto di una --
per segnare la fine delle opzioni (il echo
builtin di alcune conchiglie di non-Bourne-come fare, e zsh supporti -
per questo però), così per esempio, è difficile uscita "-n"
con echo
in molte conchiglie.
Su 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 si $POSIXLY_CORRECT
trova nell'ambiente e con la versione 4 , zsh
con la sua bsd_echo
opzione, alcuni basati su pdksh con la loro posix
opzione o se sono chiamati come sh
o meno). Quindi non è garantito che due bash
echo
secondi, anche della stessa versione, bash
si comportino allo stesso modo.
POSIX dice: se il primo argomento è -n
o qualsiasi argomento contiene barre rovesciate, il comportamento non è specificato . bash
l'eco al riguardo non è POSIX in quanto, ad esempio, echo -e
non viene emessa -e<newline>
come POSIX richiede. La specifica UNIX è più rigorosa, proibisce -n
e richiede l'espansione di alcune sequenze di escape tra cui \c
quella 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 $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.
Ciò significa che non è possibile utilizzare 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 usarlo echo
per visualizzarlo.
Questo va bene:
echo >&2 Invalid file.
Questo non è:
echo >&2 "Invalid file: $file"
(Anche se funzionerà bene con alcune echo
implementazioni (non conformi a UNIX) come quelle bash
quando l' xpg_echo
opzione 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 yash
di ECHO_STYLE=raw
(con l'avvertenza che yash
le 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 $var
seguito 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 printf
implementazioni. C'è un nucleo di funzionalità che è specificato da POSIX, ma poi ci sono molte 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 i \uxxxx
caratteri 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 printf
e 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 echo
può 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 IFS
con 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 bash
il echo
comportamento può essere modificato.
Con bash
, in fase di esecuzione, ci sono due cose che controllano il comportamento di echo
(accanto enable -n echo
o ridefinendo echo
come funzione o 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 si POSIXLY_CORRECT
trova 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 quando UNIX richiede:
$ 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
e 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 -e
in:
$ 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-default
e --enable-strict-posix-default
per lo configure
script. 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/bash
cui 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 ksh93
s' echo
comportamento può essere modificato.
In ksh93
, se echo
espande le sequenze di escape o meno e riconosce le opzioni dipende dal contenuto delle variabili di ambiente $PATH
e / o $_AST_FEATURES
.
Se $PATH
contiene un componente che contiene /5bin
o /xpg
prima del componente /bin
o /usr/bin
, si comporta in modo SysV / UNIX (espande le sequenze, non accetta le opzioni). Se trova /ucb
o /bsd
prima o se $_AST_FEATURES
7 contiene UNIVERSE = ucb
, si comporta in modo BSD 3 ( -e
per consentire l'espansione, riconosce -n
).
L'impostazione predefinita dipende dal sistema, BSD su Debian (vedere l'output di builtin getconf; getconf UNIVERSE
nelle 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 -e
dell'opzione è un po 'fuorviante qui. La maggior parte di questi echo
comportamenti diversi e incompatibili sono stati tutti introdotti in AT&T:
\n
, \0ooo
, \c
In panchina per il programmatore di lavoro 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
stesso probabilmente originariamente proveniva da bash
(CWRU / CWRU.chlog nella versione 1.13.5 menziona Brian Fox aggiungendolo nel 1992-10-18, GNU echo
copiandolo poco dopo in sh-utils-1.8 rilasciato 10 giorni dopo)
Mentre il echo
builtin del sh
o BSD ha supportato -e
dal giorno in cui hanno iniziato a utilizzare la shell Almquist per esso nei primi anni '90, l' echo
utilità autonoma fino ad oggi non lo supporta lì ( FreeBSDecho
non supporta ancora -e
, sebbene supporti -n
come Unix V7 (e anche \c
ma solo alla fine dell'ultimo argomento)).
La gestione di -e
stato aggiunto al ksh93
's echo
quando 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 echo
ora 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 echo
incorporato (vedi bug report ).
5. macOS echo
La maggior parte delle versioni di macOS ha ricevuto la certificazione UNIX da OpenGroup .
Il loro sh
built-in echo
è conforme in quanto è bash
(una versione molto vecchia) costruito con xpg_echo
abilitato per impostazione predefinita, ma la loro echo
utilità autonoma non lo è. env echo -n
non 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 è -n
o (dal 1995) se l'ultimo argomento finisce \c
, ma non supporta altre sequenze di barra rovesciata richieste da UNIX, nemmeno \\
.
6. echo
implementazioni in grado di produrre dati arbitrari alla lettera
A rigor di termini, puoi anche contare che FreeBSD / macOS /bin/echo
sopra (non il echo
builtin 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 -E
e -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_FEATURES
e 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 getconf
builtin (abilitato con builtin getconf
o tramite invocazione command /opt/ast/bin/getconf
) è l'interfaccia aastgetconf()
Ad esempio, dovresti builtin getconf; getconf UNIVERSE = att
modificare l' UNIVERSE
impostazione su att
(causando il echo
comportamento SysV tra le altre cose). Dopo averlo fatto, noterai che la $_AST_FEATURES
variabile di ambiente contiene UNIVERSE = att
.
echo -e
?