入力ファイル名と出力ファイル名に 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
以前に設定されたファイル記述子 と にリダイレクトされます。3
4
標準入力と出力を再開する
stdin
中間ファイル記述子を開きたくない場合は、代わりに、stdout
指定された入力ファイルと出力ファイルに対応するファイル記述子を直接置き換えることもできます。
#!/bin/bash
[[ $1 ]] && [[ ! -f $1 ]] && echo "file $1 dne" && exit 1
[[ $1 ]] && exec 0<$1
[[ $2 ]] && exec 1>$2
fgrep -v "stuff"
この方法の欠点は、スクリプト自体からの出力と、リダイレクトのターゲットであるコマンドの出力を区別できなくなることです。 元の方法では、スクリプトの出力を変更されていない および に送ることができますstdin
がstdout
、これはスクリプトの呼び出し元によってリダイレクトされている可能性があります。 指定された入力ファイルと出力ファイルには、スクリプトおよび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 に書き込むときは、ファイルが存在しないことを確認する必要があります。存在しない場合は上書きされます。