it-swarm.com.de

Bash: Dynamische Eingabe der Standardeingabe in einem Skript

Ich habe versucht, dies zu tun, um zu entscheiden, ob stdin in eine Datei umgeleitet werden soll oder nicht:

[ ...some condition here... ] && input=$fileName || input="&0"
./myScript < $input

Das funktioniert aber nicht, da die Variable $ input "& 0" ist und von bash als Dateiname interpretiert wird.

Ich könnte jedoch nur folgendes tun:

if [ ...condition... ];then
    ./myScript <$fileName
else
    ./myScript

Das Problem ist, dass ./myScript eigentlich eine lange Befehlszeile ist, die ich nicht duplizieren möchte, und auch keine Funktion dafür erstellen möchte, weil sie auch nicht so lang ist (es lohnt sich nicht).

Dann fiel mir ein, dies zu tun:

[ ...condition... ] && input=$fileName || input=  #empty
cat $input | ./myScript

Dafür müssen jedoch noch ein Befehl und eine Pipe (d. H. Eine Subshell) ausgeführt werden.
Gibt es einen anderen Weg, der einfacher und effizienter ist?

26
GetFree

Zunächst ist stdin der Dateideskriptor 0 (null) und nicht 1 (was stdout ist).

Sie können Dateideskriptoren duplizieren oder Dateinamen wie folgt verwenden:

[[ some_condition ]] && exec 3<"$filename" || exec 3<&0

some_long_command_line <&3

Beachten Sie, dass der angezeigte Befehl die zweite exec ausführt, wenn entweder die Bedingung false ist oder die erste exec fehlschlägt. Wenn Sie nicht möchten, dass ein Fehler auftritt, sollten Sie eine if/else verwenden:

if [[ some_condition ]]
then
    exec 3<"$filename"
else
    exec 3<&0
fi

nachfolgende Umleitungen von Dateideskriptor 3 schlagen jedoch fehl, wenn die erste Umleitung fehlschlägt (nachdem die Bedingung erfüllt war).

22
(
    if [ ...some condition here... ]; then
        exec <$fileName
    fi
    exec ./myscript
)

In einer Subshell stdin bedingt umleiten und das Skript ausführen.

7
ephemient

Die Standardeingabe kann auch durch die spezielle Gerätedatei /dev/stdin dargestellt werden, sodass die Verwendung als Dateiname möglich ist.

file="/dev/stdin"
./myscript < "$file"

Wie wäre es mit

function runfrom {
    local input="$1"
    shift
    case "$input" in
        -) "[email protected]" ;;
        *) "[email protected]" < "$input" ;;
    esac
}

Ich habe das Minuszeichen verwendet, um Standardeingaben zu kennzeichnen, da dies für viele Unix-Programme üblich ist.

Jetzt schreibst du

[ ... condition ... ] && input="$fileName" || input="-"
runfrom "$input" my-complicated-command with many arguments

Ich finde, dass diese Funktionen/Befehle, die Befehle als Argumente akzeptieren (wie xargs(1)), sehr nützlich sein können und gut zusammenstellen. 

2
Norman Ramsey

Wenn Sie vorsichtig sind, können Sie 'eval' und Ihre erste Idee verwenden.

[ ...some condition here... ] && input=$fileName || input="&1"
eval ./myScript < $input

Sie sagen jedoch, dass "myScript" tatsächlich ein komplexer Befehlsaufruf ist. Wenn es sich um Argumente handelt, die Leerzeichen enthalten können, müssen Sie sehr vorsichtig sein, bevor Sie 'eval' verwenden.

Ehrlich gesagt ist es wahrscheinlich nicht die Mühe wert, sich über die Kosten eines 'cat'-Befehls Gedanken zu machen. Es ist unwahrscheinlich, dass dies der Engpass ist.

Noch besser ist es, myScript so zu entwerfen, dass es wie ein normaler Unix-Filter funktioniert - er liest die Standardeingabe, es sei denn, es werden eine oder mehrere Dateien angegeben (z. B. cat oder grep als Beispiele). Dieses Design basiert auf langjähriger und fundierter Erfahrung - und lohnt sich daher, nachgiebig zu sein, um nicht mit Problemen wie diesem umgehen zu müssen.

2

Verwenden Sie eval :

#! /bin/bash

[ $# -gt 0 ] && input="'"$1"'" || input="&1"

eval "./myScript <$input"

Dieses einfache Stand-In für myScript

#! /usr/bin/Perl -lp
$_ = reverse

erzeugt die folgende Ausgabe:

 $ ./myDemux myScript 
 plrep/nib/rsu /! # 
 esrever = _ $ 
 
 $ ./myDemux 
 foo 
 o von 
 bar 
 rab 
 baz 
 zab 

Beachten Sie, dass auch Leerzeichen in Eingaben verarbeitet werden:

 $ ./myDemux foo\bar 
 eman ni ni ecaps a htiw Elif 

Um die Eingabe auf myScript zu leiten, verwenden Sie Prozessersetzung :

 $ ./myDemux <(md5sum /etc/issue)[.____.? eussi/cte/ 01672098e5a1807213d5ba16e00a7ad0 

Beachten Sie Folgendes, wenn Sie versuchen, die Ausgabe direkt zu übergeben, wie in

 $ md5sum/etc/issue | ./myDemux

es wird auf Eingabe vom Terminal warten, während die Antwort von ephemient diesen Mangel nicht hat.

Eine geringfügige Änderung bewirkt das gewünschte Verhalten:

#! /bin/bash

[ $# -gt 0 ] && input="'"$1"'" || input=/dev/stdin
eval "./myScript <$input"
1
Greg Bacon

die Leute zeigen Ihnen sehr lange Skripte, aber .... Sie erhalten Bash-Trap:) Sie müssen alles in Bash. zitieren. Beispielsweise möchten Sie eine Listendatei mit dem Namen & 0.

dateiname = '& 0' #right ls $ Dateiname #wrong! $ ersetze $ datein und interpretiere & 0 ls "$ filename" #right

ein anderes, Dateien mit Leerzeichen.

dateiname = 'einige Datei mit Leerzeichen' ls $ Dateiname #Wrong, Bash schneidet das erste und letzte Leerzeichen aus und reduziert mehrere Leerzeichen zwischen den Wörtern mit und dem Leerzeichen ls "$ Dateiname"

das gleiche ist in deinem Skript. Bitte austauschen:

./myScript < $input

zu

./myScript < "$input"

es ist alles. bash hat mehr Traps. Ich schlage vor, "$ file" aus demselben Grund zu zitieren. Leerzeichen und andere Zeichen, die interpretiert werden können, machen immer Probleme.

aber was ist mit/dev/stdin? Dies ist nur verwendbar, wenn Sie stdin umgeleitet haben und etwas auf echte stdin drucken möchten.

ihr Skript sollte also so aussehen:

[ ...some condition here... ] && input="$fileName" || input="&0"
./myScript < "$input"
0
Znik