ファイル名引数を使用する方法、または stdin、stdout をデフォルトにする方法 (概要)

ファイル名引数を使用する方法、または stdin、stdout をデフォルトにする方法 (概要)

入力ファイル名と出力ファイル名に 0、1、または 2 個の引数を取り、よりクリーンで柔軟な方法で bash スクリプトの引数としてファイル名を処理したいです。

  • args = 0 の場合、stdin から読み取り、stdout に書き込みます。
  • args = 1 の場合、$1 から読み取り、stdout に書き込みます。
  • args = 2 の場合、$1 から読み取り、$2 に書き込みます

bash スクリプトのバージョンをより簡潔に、短くするにはどうすればよいでしょうか?

これが今私が持っているもので、機能しているが、きれいではない。

#!/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

Perl版はよりクリーンで、

#!/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 "$_"; } }

答え1

私はもっ​​と活用したいI/Oリダイレクト:

#!/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

説明

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

    入力ファイルがコマンドライン引数として指定されているかどうか、またそのファイルが存在するかどうかをテストします。

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

    が設定されている場合$1、つまり入力ファイルが指定されている場合、指定されたファイルはファイル記述子 で開かれ3、そうでない場合はstdinファイル記述子 で複製されます3

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

    同様に、 が設定されている場合$2、つまり出力ファイルが指定されている場合、指定されたファイルはファイル記述子 で開かれ4、それ以外の場合はstdoutファイル記述子 で複製されます4

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

    最後にfgrepが呼び出され、そのstdinと がそれぞれstdout以前に設定されたファイル記述子 と にリダイレクトされます。34

標準入力と出力を再開する

stdin中間ファイル記述子を開きたくない場合は、代わりに、stdout指定された入力ファイルと出力ファイルに対応するファイル記述子を直接置き換えることもできます。

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

この方法の欠点は、スクリプト自体からの出力と、リダイレクトのターゲットであるコマンドの出力を区別できなくなることです。 元の方法では、スクリプトの出力を変更されていない および に送ることができますstdinstdout、これはスクリプトの呼び出し元によってリダイレクトされている可能性があります。 指定された入力ファイルと出力ファイルには、スクリプトおよびstdinとは異なる対応するファイル記述子を介してアクセスできますstdout

答え2

いかがでしょうか:

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

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

/dev/std(in|out|err)注意してくださいPOSIX標準にはないしたがって、これはこれらの特殊ファイルをサポートするシステムでのみ機能します。

これも正常な入力を前提としており、リダイレクトする前にファイルの存在をチェックしません。

答え3

出力がいつもstdout にリダイレクトする場合は、次のワンライナーを使用できます。

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

答え4

これが「よりクリーン」かどうかはわかりませんが、ここにいくつかの提案があります (これはテストされていないコードです)。 の使用はexec(Thomas Nyman によると) 安全上の問題を引き起こす可能性があるため、注意して扱う必要があります。

まず、関数内に繰り返しコードを配置します。

# 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'
}

fgrepここでは、の代わりにcatが役に立ちます。次に 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

もう 1 つの代替案 (これは簡潔で非常にコンパクトです) は、命令を配列にロードし、関数ポインターのような $# の値を介してアクセスすることです。is_file上記の関数を考えると、Bash コードは次のようになります。

# 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' 

構文については 100% 理解しているわけではありませんが、二重引用符はエスケープする必要があります。ファイル パスを含む変数は二重引用符で囲むのが最適です。

追加の注意: $2 に書き込むときは、ファイルが存在しないことを確認する必要があります。存在しない場合は上書きされます。

関連情報