So verwenden Sie Dateinamenargumente oder verwenden standardmäßig stdin oder stdout (kurz)

So verwenden Sie Dateinamenargumente oder verwenden standardmäßig stdin oder stdout (kurz)

Ich möchte Dateinamen als Argumente in einem Bash-Skript sauberer und flexibler behandeln und dabei 0, 1 oder 2 Argumente für Eingabe- und Ausgabedateinamen verwenden.

  • wenn args = 0, von stdin lesen, in stdout schreiben
  • wenn args = 1, von $1 lesen, in stdout schreiben
  • wenn args = 2, von $1 lesen, nach $2 schreiben

Wie kann ich die Bash-Skriptversion übersichtlicher und kürzer machen?

Hier ist, was ich jetzt habe, was funktioniert, aber nicht sauber ist,

#!/bin/bash
if [ $# -eq 0 ] ; then #echo "args 0"
    fgrep -v "stuff"
elif [ $# -eq 1 ] ; then #echo "args 1"
    f1=${1:-"null"}
    if [ ! -f $f1 ]; then echo "file $f1 dne"; exit 1; fi
    fgrep -v "stuff" $f1 
elif [ $# -eq 2 ]; then #echo "args 2"
    f1=${1:-"null"}
    if [ ! -f $f1 ]; then echo "file $f1 dne"; exit 1; fi
    f2=${2:-"null"}
    fgrep -v "stuff" $f1 > $f2
fi

Die Perl-Version ist sauberer,

#!/bin/env perl
use strict; 
use warnings;
my $f1=$ARGV[0]||"-";
my $f2=$ARGV[1]||"-";
my ($fh, $ofh);
open($fh,"<$f1") or die "file $f1 failed";
open($ofh,">$f2") or die "file $f2 failed";
while(<$fh>) { if( !($_ =~ /stuff/) ) { print $ofh "$_"; } }

Antwort1

Ich würde mehr nutzen vonE/A-Umleitung:

#!/bin/bash
[[ $1 ]] && [[ ! -f $1 ]] && echo "file $1 dne" && exit 1
[[ $1 ]] && exec 3<$1 || exec 3<&0
[[ $2 ]] && exec 4>$2 || exec 4>&1
fgrep -v "stuff" <&3 >&4

Erläuterung

  • [[ $1 ]] && [[ ! -f $1 ]] && echo "file $1 dne" && exit 1

    Testen Sie, ob eine Eingabedatei als Befehlszeilenargument angegeben wurde und ob die Datei existiert.

  • [[ $1 ]] && exec 3<$1 || exec 3<&0

    Wenn $1gesetzt ist, d. h. eine Eingabedatei angegeben wurde, wird die angegebene Datei am Dateideskriptor geöffnet 3, andernfalls stdinwird am Dateideskriptor dupliziert 3.

  • [[ $2 ]] && exec 4>$2 || exec 4>&1

    Entsprechend wird, wenn $2gesetzt ist, d. h. eine Ausgabedatei angegeben wurde, die angegebene Datei am Dateideskriptor geöffnet 4, andernfalls stdoutwird am Dateideskriptor dupliziert 4.

  • fgrep -v "stuff" <&3 >&4

    Zuletzt fgrepwird aufgerufen, wobei stdinund stdoutzu den zuvor festgelegten Dateideskriptoren 3bzw. umgeleitet werden 4.

Standard-Ein- und Ausgabe erneut öffnen

Wenn Sie keine Zwischendateideskriptoren öffnen möchten, können Sie alternativ die Dateideskriptoren direkt stdinund stdoutentsprechend den angegebenen Eingabe- und Ausgabedateien ersetzen:

#!/bin/bash
[[ $1 ]] && [[ ! -f $1 ]] && echo "file $1 dne" && exit 1
[[ $1 ]] && exec 0<$1
[[ $2 ]] && exec 1>$2
fgrep -v "stuff"

Ein Nachteil dieses Ansatzes besteht darin, dass Sie die Möglichkeit verlieren, die Ausgabe des Skripts selbst von der Ausgabe des Befehls zu unterscheiden, der das Ziel der Umleitung ist. Beim ursprünglichen Ansatz können Sie die Skriptausgabe an das unveränderte stdinund weiterleiten stdout, das wiederum vom Aufrufer des Skripts umgeleitet worden sein könnte. Auf die angegebenen Eingabe- und Ausgabedateien konnte weiterhin über die entsprechenden Dateideskriptoren zugegriffen werden, die sich vom Skript stdinund unterscheiden stdout.

Antwort2

Wie wäre es mit:

  input="${1:-/dev/stdin}"
  output="${2:-/dev/stdout}"
  err="${3:-/dev/stderr}"

  foobar <"$input" >"$output" 2>"$err"

Beachten Sie bitte, /dev/std(in|out|err)dassnicht im POSIX-StandardDies funktioniert also nur auf Systemen, die diese speziellen Dateien unterstützen.

Dies setzt auch vernünftige Eingaben voraus: Vor der Umleitung wird nicht geprüft, ob Dateien vorhanden sind.

Antwort3

wenn es Ihnen nichts ausmacht, dass die Ausgabestetsauf stdout umgeleitet werden, können Sie den folgenden Einzeiler verwenden:

cat $1 |fgrep -v "stuff" | tee  

Antwort4

Ich weiß nicht, ob das „sauberer“ ist, aber hier sind einige Vorschläge (das ist kein getesteter Code). Die Verwendung von exec(laut Thomas Nyman) kann zu Sicherheitsproblemen führen und sollte mit Vorsicht behandelt werden.

Platzieren Sie zunächst sich wiederholenden Code in einer Funktion.

# die <message>
function die(){
    echo "Fatal error: $1, exiting ..." >&2
    exit 1
}

# is_file <file-path>
function is_file(){
    [[ -n "$1" && -f "$1" ]] && return 0
    die 'file not found'
}

Hier verwenden Sie statt fgrep, catIhr Freund. Verwenden Sie dann select case:

case $# in
    0) cat ;;                                  # accepts stdin to stdout.
    1) is_file "$1"; cat "$1" ;;               # puts $1 to stdout.
    2) is_file "$1"; cat "$1" > "$2" ;;        # puts $1 to $2.
    *) die 'too many arguments' ;;
esac

Eine weitere Alternative (die sauber und sehr kompakt ist) besteht darin, die Anweisungen in ein Array zu laden und dann über den Wert von $# darauf zuzugreifen, so etwas wie einen Funktionszeiger. Bei der is_fileobigen Funktion sieht der Bash-Code etwa so aus:

# action array.
readonly do_stuff=(
    'cat'                                  # 0 arg.
    'is_file \"$1\"; cat \"$1\"'           # 1 arg.
    'is_file \"$1\"; cat \"$1\" > \"$2\";' # 2 args.
)

# Main - just do:
[[ $# -le 2 ]] && ${do_stuff[$#]} || die 'too many arguments' 

Ich bin mir mit der Syntax nicht 100 % sicher, aber die Anführungszeichen müssen maskiert werden. Variablen, die Dateipfade enthalten, werden am besten in doppelte Anführungszeichen gesetzt.

Ein zusätzlicher Hinweis: Beim Schreiben nach $2 sollte wahrscheinlich überprüft werden, dass die Datei nicht existiert, da sie sonst überschrieben wird.

verwandte Informationen