入力ファイル名と出力ファイル名に0、1、または2個の引数を使用して、bashスクリプトの引数としてファイル名をよりクリーンで柔軟な方法で処理したいと思います。
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 "$_"; } }
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
とは異なる対応するファイル記述子を介してアクセスできます。
どうですか:
_ input="${1:-/dev/stdin}"
output="${2:-/dev/stdout}"
err="${3:-/dev/stderr}"
foobar <"$input" >"$output" 2>"$err"
_
/dev/std(in|out|err)
は POSIX標準にはありません であるため、これはこれらの特別なファイルをサポートするシステムでのみ機能することに注意してください。
これはまた、正しい入力を前提としています。リダイレクトする前にファイルの存在をチェックしません。
出力が常に stdoutにリダイレクトされることを気にしない場合は、次のワンライナーを使用できます。
cat $1 |fgrep -v "stuff" | tee
exec <foo
を使用して、スクリプトの入力をファイルfoo
からリダイレクトし、同様にexec >foo
を出力にリダイレクトすることができます。
#!/bin/sh
set -e
if [ $# -ne 0 ]; then
exec <"$1"
shift
fi
if [ $# -ne 0 ]; then
exec >"$1"
shift
fi
fgrep -v "stuff"
このスタイルは簡潔ですが、詳細なエラー報告には適していません。
これが「よりクリーン」であるかどうかはわかりませんが、いくつかの提案があります(これはテスト済みのコードではありません)。 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
が友達です。次に、選択したケースを使用します。
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に書き込むときは、ファイルが存在しないことを確認する必要があります。そうしないと、ファイルが上書きされます。