web-dev-qa-db-ja.com

ファイル名引数の使用方法またはデフォルトの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 "$_"; } }
5
ChuckCottrill

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が呼び出され、そのstdinstdoutがそれぞれ以前に設定されたファイル記述子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"

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

8
Thomas Nyman

どうですか:

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

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

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

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

3
Joseph R.

出力が常に stdoutにリダイレクトされることを気にしない場合は、次のワンライナーを使用できます。

cat $1 |fgrep -v "stuff" | tee  
1
umläute

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に書き込むときは、ファイルが存在しないことを確認する必要があります。そうしないと、ファイルが上書きされます。

0
AsymLabs