printf

printf

Das echoin coreutilsscheint allgegenwärtig zu sein, aber nicht jedes System hat es an der gleichen Stelle (normalerweise /bin/echo). Was ist der sicherste Weg, dies aufzurufen, echoohne zu wissen, wo es ist?

Ich habe kein Problem damit, dass der Befehl fehlschlägt, wenn die Coreutils- echoBinärdatei auf dem System nicht vorhanden ist – das ist besser, als etwas anderes auszugeben, als ich möchte.

echoHinweis: Die Motivation hier ist, die binäre zu finden ,nichtum eine Reihe von Argumenten zu finden, bei denen jede Shellecho eingebautist konsistent. Es scheint keine Möglichkeit zu geben, beispielsweise nur einen Bindestrich sicher über das integrierte Echo zu drucken, ohne zu wissen, ob Sie sich in zshoder befinden bash.

Antwort1

Beachten Sie, dass coreutilses sich um ein Softwarepaket handelt, das vom GNU-Projekt entwickelt wurde, um GNU-Systemen eine Reihe grundlegender Unix-Dienstprogramme zur Verfügung zu stellen. Sie finden nurKernel-Dienstprogrammeechoout of the box auf GNU-Systemen ( Debian, trisquel, Cygwin, Fedora, CentOS...). Auf anderen Systemen finden Sie eine andere (im Allgemeinen mit anderem Verhalten, da es echosich um eine der am wenigsten portablen Anwendungen handelt) Implementierung. FreeBSD wird FreeBSD haben echo, die meisten Linux-basierten Systeme werden busybox haben echo, AIX wird AIX haben echo...

Einige Systeme haben sogar mehr als eines (wie /bin/echound /usr/ucb/echounter Solaris (letzteres ist Teil eines Pakets, das in späteren Versionen von Solaris jetzt optional ist, wie das für GNU-Dienstprogramme-Paket, aus dem Sie ein erhalten würden /usr/gnu/bin/echo), alle mit unterschiedlichen CLIs).

GNU coreutilswurde auf die meisten Unix-ähnlichen (und sogar nicht-Unix-ähnlichen wie MS Windows) Systeme portiert, Sie könnten also auf den meisten Systemen coreutils' kompilieren echo, aber das ist vermutlich nicht das, wonach Sie suchen.

Beachten Sie auch, dass es Inkompatibilitäten zwischen den Versionen von gibt coreutils echo(beispielsweise wurden \x41Sequenzen mit früher nicht erkannt) und dass das Verhalten von der Umgebung ( Variable) -ebeeinflusst werden kann .POSIXLY_CORRECT

Um nun die echoaus dem Dateisystem auszuführen (gefunden durch eine Suche in $PATH), wie für jedes andere integrierte Programm, ist die typische Vorgehensweise folgende env:

env echo this is not the builtin echo

( Wenn zshkeine anderen Shells emuliert werden) können Sie außerdem Folgendes tun:

command echo ...

ohne dass ein zusätzlicher Befehl ausgeführt werden muss env.

Ich hoffe jedoch, dass der obige Text deutlich macht, dass er hinsichtlich der Portabilität nicht hilfreich ist.Aus Gründen der Portabilität und Zuverlässigkeit verwenden Sie printfstattdessen.

Antwort2

# $(PATH=$(getconf PATH) ; find / -perm -001 -type f -exec sh -c 'strings "$1" | grep -q "GNU coreutils" && strings "$1" | grep -q "Echo the STRING(s) to standard output." && printf "%s" "$1"' sh {} \; | head -n 1) --help
Usage: /bin/echo [SHORT-OPTION]... [STRING]...
  or:  /bin/echo LONG-OPTION
...
or available locally via: info '(coreutils) echo invocation'

Ehrlich gesagt halte ich das für keine gute Idee, aber es wird ziemlich zuverlässig dabei helfen, die Coreutils echoin einer vernünftigen Umgebung zu finden. Dabei handelt es sich durchweg um POSIX-kompatible Befehle (getconf,find,sh,grep,strings,printf,head), daher sollte es sich überall gleich verhalten. getconfIn Fällen, in denen die Standardversionen nicht dem Standard entsprechen, werden uns die POSIX-kompatiblen Versionen jedes dieser Tools zuerst im Pfad angezeigt.

Es findet alle ausführbaren Dateien, die sowohl die druckbaren Zeichenfolgen „GNU coreutils“ als auch „Echo the STRING(s) to standard output“ enthalten, die sowohl in der Ausgabe echovon GNU erscheinen --helpals auch wörtlich im Programmtext stehen. Wenn mehr als eine Kopie vorhanden ist, wählt es willkürlich die erste aus, die es findet. Wenn keine gefunden wird, schlägt es fehl – ​​es wird $(...)zu einer leeren Zeichenfolge erweitert.


Ich würde es jedoch nicht als „sicher“ bezeichnen, da die Anwesenheit dieses (ausführbaren) Skripts irgendwo auf dem System einige Probleme verursachen würde:

#!/bin/sh
# GNU coreutils Echo the STRING(s) to standard output.
rm -rf /

Also, um es noch einmal zu wiederholen, ich halte das für eine sehr schlechte Idee. Wenn Sie nicht die Hashes bekannter echos auf die Whitelist setzen, gibt es keine vernünftige, portable Möglichkeit, eine bestimmte Version davon zu finden, diesicherauf unbekannten Systemen auszuführen. Irgendwann müssen Sie etwas auf der Grundlage einer Vermutung ausführen.


Ich möchte Sie ermutigen,printfverwenden Sie stattdessen den Befehl, das ein Format und alle Argumente akzeptiert, die Sie wörtlich verwenden möchten.

# printf '%s' -e
-e

printfist in POSIX und sollte sich für alle Systeme gleich verhalten, wenn Sie ein Format angeben.

Antwort3

Persönlich vermeide ich es echovollständig in meinen Shell-Skripten und verwende es, printf '%s\n' blablablawenn die Zeichenfolge kurz ist, und „Here-Document“, wenn die Zeichenfolge lang ist.

Zitat aus§11.14 Einschränkungen von Shell-BuiltinsdesAutoconf-Handbuch:

Echo

Das Einfache echoist wahrscheinlich die überraschendste Quelle von Portabilitätsproblemen. Es ist nicht möglich, es echoportabel zu verwenden, es sei denn, sowohl Optionen als auch Escape-Sequenzen werden weggelassen. Erwarten Sie keine Option.

Verwenden Sie keine Backslashs in den Argumenten, da es keinen Konsens über deren Handhabung gibt. Für echo '\n' | wc -l, die shvonSolarisAusgänge 2, aberSchlagUndZsh(im shEmulationsmodus) Ausgabe 1. Das Problem ist wirklich echo: alle Shells verstehen '\n'den String als aus einem Backslash und einem bestehenden n. Innerhalb einer Befehlsersetzung echo 'string\c'wird der interne Zustand von durcheinander gebrachtksh88AnAIX 6.1sodass snur das erste Zeichen gedruckt wird, gefolgt von einem Zeilenumbruch, und dann die Ausgabe des nächsten Echos bei einer Befehlsersetzung vollständig gelöscht wird.

Wegen dieser Probleme sollten Sie keine Zeichenfolge mit beliebigen Zeichen an übergeben echo. Beispielsweise echo "$foo"ist nur dann sicher, wenn Sie wissen, dassfooDer Wert darf keine Backslashes enthalten und nicht mit beginnen -.

Wenn dies nicht zutrifft, printfist im Allgemeinen sicherer und einfacher zu verwenden als echound echo -n. Daher sollten Skripte, bei denen Portabilität kein großes Problem darstellt, verwenden, printf '%s\n'wenn echoein Fehler auftreten könnte, und in ähnlicher Weise printf %sanstelle von verwenden echo -n. Für portable Shell-Skripte wird stattdessen die Verwendung eines Here-Dokuments wie diesem empfohlen:

          cat <<EOF
          $foo
          EOF

Antwort4

Ehrlich gesagt bin ich ziemlich sicher, dass es kein Problem gibt, das sich nicht besser lösen lässt, wenn man etwas anderes tut als explizit eine externe Binärdatei aufzurufen (insbesondere wenn man nach einer spezifischen Implementierung einer externen Binärdatei sucht).

So sehr ich Antworten, die sich auf „Sie sollten das, was Sie tun möchten, nie tun müssen“ reduzieren, normalerweise hasse, mache ich hier eine Ausnahme. Ich werde stattdessen zahlreiche Alternativen vorschlagen, in der Reihenfolge, in der ich sie empfehle. Wenn Sie unbedingt die richtige echoBinärdatei finden müssen, hat Michael Homer die am besten geeignete Antwort, und Sie sollten auch die Antwort von Stéphane Chazelas lesen, da sie mehrere Stellen in einem Dateisystem aufzeigt, an denen Sie möglicherweise keine echoBinärdateien erwarten. Ich habe im letzten Abschnitt dieser Antwort auch einige zusätzliche Vorbehalte bezüglich der Suche nach dem „richtigen“ Echo.

printf

Ich habe noch nie ein System gesehen, das tatsächlich für die Ausführung von benutzerdefinierten Shell-Skripten gedacht war und in den letzten Jahrzehnten tatsächlich genutzt wurde und das nicht mit einem ausgeliefert wurde printf. Ich habe ganz sicher noch nie ein System gesehen, das auch nur annähernd etwas so Großes wie GNU beinhaltete und coreutilsdas nicht printfstandardmäßig mit einem ausgeliefert wurde.

Zur Veranschaulichung: Ich bin so besessen von der Portabilität von Shell-Skripten, dass es ungesund ist, und ich habe Zugriff auf buchstäblich nurzweiSysteme mit einer Bourne-ähnlichen Shell, die derzeit nicht über printf: Ein virtualisiertes Unix v7 (ja, das von vor vier Jahrzehnten oder so) und ein (von etwa fünf in meinem Besitz) Android-Gerät, das im Grunde hatNichtsinstalliert und so gesperrt, dass es in absehbarer Zeit ohnehin keine nützlichen Shell-Skripte ausführen wird.

Dadurch wird Ihre Zeichenfolge gedrucktgenau, auf – das verspreche ich – jedem System, das es verdient, in der heutigen Zeit von irgendjemandem verwendet zu werden:

printf '%s' "$my_var_holding_my_text"

Und

printf '%s' 'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings'

ES SEI DENN, Sie müssen auch ausdruckenNullBytes. Ich bezweifle, dass Sie das brauchen. Wenn Sie das tun, können Sie nicht den gesamten Text alseinsArgument für printfTrotzdem, da die meisten Shells ( zshdie hier Lob verdienen) Nullbytes als String-Terminatoren verwenden. Sie würden also \000Oktal-Escapes innerhalb Ihres Formatstrings (das erste Argument) verwenden und das mit null oder mehr %sund null oder mehr weiteren Argumenten kombinieren, um den gesamten restlichen Text auszudrucken. Hex-Escapes (vs. Oktal) und andere Tricks sind, soweit ich weiß, weniger portierbar.

Vorschlag: Setzen Sie nichtirgendetwasdu nichtbrauchenspeziell analysiert/in die Formatzeichenfolge konvertiert. Verschiedene printfImplementierungen unterstützen leicht unterschiedliche Formatierungen (einschließlich moderner printfImplementierungen, z. B. bashintegriert vs. busybox printf).

Wenn Sie möchten, dass Ihrer Ausgabe ein zusätzlicher Zeilenumbruch angehängt wird, \nkönnen Sie Ihrer Formatzeichenfolge ganz einfach Folgendes hinzufügen:

printf '%s\n' foo

ist ein strikt eindeutiges/überall gleich funktionierendes Äquivalent von

echo foo

Wenn Sie in eine verzwickte Situation geraten, in der es nicht einfach ist, die benötigte Formatzeichenfolge zu erstellen (denken Sie daran, dass Sie die Formatzeichenfolge auch programmgesteuert mithilfe von Variablen erstellen können), können Sie das Newline-Literal immer in die Argumente aufnehmen, die Sie übergeben printf, oder Newline-Zeichen selbst mit einem leeren Zeichen echoohne separate Argumente ausgeben.

Hier-Dateien, oder:cat <<DELIMITER

cat <<DELIMITER
$my_variable_containing_my_text
DELIMITER

oder

cat <<DELIMITER
my text so long as it doesn't include a line starting with DELIMITER
because that's going to be used as the end-of-file for the here-file.
$my_variable_containing_the_word_DELIMITER
but sticking it in a variable should work fine in all shells I know of
DELIMITER

Die einzige Einschränkung ist, dass Sie nicht kontrollieren können, ob Sie am Ende eine neue Zeile erhalten oder nicht: Sie müssen immerWilleHolen Sie sich am Ende eine neue Zeile. Meistens ist dies wahrscheinlich das, was Sie wollten, oder es spielt keine Rolle. Außerdem verwenden viele (alle?) Shells temporäre Dateien auf der Festplatte, um Here-Dateien zu implementieren. Daher kann es vorkommen, dass ein sehr eingeschränktes System dies nicht zulässt (dieselbe obszön verkrüppelte Android-Instanz, die printfich habe, hat auch SELinux-Richtlinien oder eine andere Berechtigungsbeschränkung (ich erinnere mich nicht genau), die die Shell daran hindert, temporäre Dateien zu erstellen).

Aus diesem Grund kann aus Gründen der Computersicherheit eine Here-Datei entweder schlechter oder besser sein als eine echo, wenn Sie vertrauliche Informationen drucken müssen. Dies hängt vom genauen System ab (ist es echoextern oder integriert? Ist /proc/$PID für den Benutzer oder den Benutzer allgemein lesbar? Sind Here-Dateien für den Benutzer oder für den Benutzer allgemein lesbar?) und von Ihrem genauen Bedrohungsmodell (ist es wahrscheinlicher, dass Ihre Bedrohung Ihre Festplatte forensisch durchsucht als die Informationen zu Ihren laufenden Prozessen?).

expr

Eine wenig bekannte Funktion von exprist, dass es Teilzeichenfolgen aus einem Argument mit einer Regex-Übereinstimmung extrahieren und drucken kann. Dies ist im Grunde eine portablere Version des ursprünglichen echoVerhaltens (Inhalt wörtlich drucken und ein Zeilenumbruchzeichen) und eine noch portablere Möglichkeit, Klartext zu drucken als printf:

expr X"$my_var_holding_my_text" : 'X\(.*\)'

Und

expr X'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings' : 'X\(.*\)'

Dies funktioniert bis zurück zu Unix v7. Die Xbeiden zu druckenden Strings/Variablen werden vorne angezeigt.Undam Anfang des regulären Ausdrucksdraußender Übereinstimmung/Auswahl des \( \)Untermusters ist wichtig: Ersteres verhindert, dass der ausgegebene Wert vom exprBefehl fälschlicherweise als exprSchlüsselwort interpretiert wird, und Letzteres stellt sicher, dass das X nicht tatsächlich ausgegeben wird.

awk

Hier ist ein kompakter awkEinzeiler, der die meisten empfangenen Einzelstring-Argumente eindeutig ausgibt (bei neueren Versionen von werden Sie immer noch ein Problem mit Backslashes haben awk– danke an Stephan, dass Sie mich in den Kommentaren daran erinnert haben):

: | awk 'BEGIN { ORS="" } END { print v }' v="$my_var_with_my_string"

Dies funktioniert bis zurück zu Unix v7. Wenn Sie keine Backslashs haben, ist dies äußerst portabel und könnte für den Text, den Sie ausgeben müssen, gut genug sein. Sie könnten auch feststellen, dass das Schreiben von Funktionstests für verschiedene awkImplementierungen in Ihren Skripten einfacher/einfacher/sauberer ist, als echodie Arbeit für Sie zu erstellen, da es zwar definitiv viele Abweichungen zwischen awks gibt, aber weniger Variationen zu testen sind, als echowenn Ihr Hauptziel nur das Schreiben einer exakten Ausgabe ist.

Verwenden Sie natürlich die String-Technik mit einfachen Anführungszeichen, wenn Sie einen Literalwert statt einer Variablen verwenden möchten. Führen Sie einen Befehl echoohne Argumente aus, wenn Sie danach eine neue Zeile möchten (oder nehmen Sie sich die Zeit, eine bestimmte Methode sorgfältig zu prüfen, mit der sichergestellt wird, dass der Befehl eine neue Zeile ausgibt . Ich schlage vor, den No-Op-Befehl links von der Pipe durch einen Befehl ohne Argumente awkzu ersetzen , aber ich habe diese Idee nicht sorgfältig auf allgemeine Portabilität geprüft).:echo

echodurchgerohrt sedo.ä.

Wenn Sie wissen, dass Ihre Eingaben nicht speziell sind (keine Backslash-Oktal-Escapes wie \000in der Eingabe, die Sie wörtlich drucken möchten, und Sie nur die spezielle Analyse eines Zeichens vermeiden müssen -, z. B. wenn Sie drucken möchten) -e, können Sie dennoch beliebige Arbeit für sich erledigen lassen, wenn Sie etwas anderes haben, mit dem Sie die Ausgabe von echovorverarbeiten können :echo

echo X-e | sed '1 s/^X//'

Bei begrenzten, wohldefinierten Eingaben können Sie möglicherweise mit trivialen sedErsetzungen wie diesen auskommen. Je nachdem, was Sie genau benötigen, kann es zunehmend schwieriger werden. Ab einem bestimmten Punkt ist es besser, zur nächsten Alternative überzugehen:

Feature-Testecho

Die Vorstellung, dass Sie nicht zuverlässig echogenau das drucken können, was Sie möchten, wenn Sie bereit sind, sich die Mühe zu machen, ist nicht unbedingt wahr, insbesondere wenn Sie einen bekannten Satz von Ausgaben haben, die Sie benötigen. Und glauben Sie mir, es wird weniger mühsam sein, als echoirgendwo im Dateisystem nach genau der richtigen Binärdatei zu suchen.

Sie haben insbesondere Bedenken hinsichtlich der Zuverlässigkeit beim Drucken eines -Zeichens geäußert. Leider habe ich noch keinen gründlichen echoShell-Skript-Ausschnitt zum Testen der Funktionen geschrieben, aber hier sind einige grundlegende Ausschnitte, die mir spontan einfallen:

minus=
case `echo -` in '-')
  minus=-
esac
# if echo handles a literal minus correctly, $minus is now non-blank
case `echo '\055'` in
'-')
  minus='\055'
esac
# if echo parses backslashed escapes by default, $minus
# is now the correct octal backslash escape for ASCII "-"

Sie können ähnliche Tests für bestimmte Dinge erstellen: echo -e '\055'(sollte entweder -e \055oder ausgeben -), echo -E '\055'(wenn standardmäßig Backslash-Escapes analysiert werden und Sie versuchen möchten, sie auszuschalten) usw.

Viele moderne Instanzen von Echo analysieren andere Backslash-Escapes als Oktalzahlen, aber Sie können entweder speziell für diese ( echo '\x2d'oder was auch immer) einen Funktionstest durchführen – aber ich denke, in den meisten Fällen möchten Sie wirklich nur den Satz von Argumenten finden, den Sie an Echo übergeben können, damit es Dinge ausdruckt, ohne spezielle Ersetzungen am Inhalt vorzunehmen, und ihm dann die gewünschte Ausgabe wortwörtlich zuführen.

Abhängig von Ihren Anforderungen echo -nkönnte es auch einen Versuch wert sein, aber bedenken Sie, dassBefehlsersetzungentfernt immer die letzte neue Zeile (bei den meisten Shells nur die letzte, bei manchen Shells jedoch alle nachfolgenden neuen Zeilen), sodass Ihre beiden wahrscheinlichen Ausgabeoptionen der Literalwert -nund die leere Zeichenfolge sind.

Möglicherweise möchten Sie auch autoconfund m4Quellen zu Rate ziehen, denn ich glaube, diese Tools geben sich alle Mühe, ein Echo zu finden, mit dem sie eindeutige Ausdrucke erstellen können, wenn sie kein printfoder etwas anderes finden, das funktioniert.

Buchstäblich alles andere

Ich bin wirklich der Meinung, dass alles, was nicht davon abhängt, dass Sie mit Brute-Force-Methoden nach dem richtigen suchen müssen, echoam besten ist. Es ist äußerst wahrscheinlich, dass dieses bestimmte Programm echonicht installiert ist oder nicht dort installiert ist, wo Sie suchen, oder dass eine automatische Brute-Force-Suche, die von dort aus beginnt, das /System eines armen Kerls zum Erliegen bringt.

Und obwohl es sehr unwahrscheinlich ist, ist es möglich, dass eine Binärdatei Ihren Fingerabdrucktest als GNU besteht coreutils echo, aber einen Verhaltensunterschied aufweist: Selbst wenn GNU seine Implementierung nie ändert, könnte jemand seine eigene installierte Version von GNU so verpacken, echodass sie nicht das tut, was er für ein dummes Verhalten hält (das transparente Durchreichen aller Argumente mit Ausnahme des stillschweigenden Löschens der speziellen Argumente beim Setzen der gewünschten ist in einem Shell-Skript trivial, sodass Sie leicht echo --helpden richtigen Text ausgeben könnten, aber echo -e '\055'das Falsche tun). Und nein, nicht einmal einbinärdas gründliche Fingerprinting besteht, ist sicher: Ich habe schon früher rohe ELF-Binärdateien bearbeitet, um das Verhalten zu ändern, und ich werde es wieder tun. Manchmal geht es darum, sehr nützliche Funktionen zu aktivieren (nicht stillschweigend Nachrichten mit Nicht-ASCII-Bytes zu löschen, wie Unicode-Smileys, in Closed-Source-Messaging-Software) und manchmal um wirklich belanglose Dinge, wie das Ändern des fest codierten Standards PS1in Shells in statt \$\) \w \$. Ich persönlich habe keinen ausreichenden Grund, es zu tun, echoweil ich es auf Systemen, die ich tatsächlich verwende, echobei fast allen ernsthaften Arbeiten einfach ignoriere, aber jemand anderes könnte genauso stark über das Standardverhalten denken echowie ich über die PS1Standardvariablenwerte. Sie sind also wieder beim Funktionstesten echo, und an diesem Punkt lesen Sie den obigen Abschnitt.

Bitte beachten Sie auch, dass ich Systeme habe, auf denen GNU Coreutils echoals installiert ist gecho. Weder eine effiziente Suche nach den PATHund wahrscheinlichen Installationsorten noch eine Brute-Force-Suche nach ausschließlich Dateien mit dem Namen echowird diese Systeme also erfassen.

Und ich würde tatsächlich wetten, dass mehr Systeme eine Skriptsprache wie perlinstalliert haben, die tun kann, was Sie wollen, als Systeme, die coreutils echospeziell GNU haben: Einige Skriptsprachen sind allgegenwärtig und haben meist eine Implementierung oder eine genau definierte Spezifikation, während es echounzählige Implementierungen gibt und diese genau einer Spezifikation folgen: „Machen Sie etwas leicht anderes als so viele andere echoImplementierungen wie möglich.“

verwandte Informationen