파일 이름 인수를 사용하거나 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"

이 접근 방식의 단점은 스크립트 자체의 출력과 리디렉션 대상인 명령의 출력을 구별하는 기능이 없다는 것입니다. 원래 접근 방식에서는 스크립트 출력을 수정되지 않은 stdin및 로 보낼 수 있으며 stdout, 이는 결국 스크립트 호출자에 의해 리디렉션되었을 수 있습니다. 지정된 입력 및 출력 파일은 스크립트 stdinstdout.

답변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

이것이 '더 깔끔한' 것인지는 모르겠지만 여기에 몇 가지 제안 사항이 있습니다(이것은 테스트된 코드가 아닙니다). (Thomas Nyman에 따라) 을 사용하면 exec안전 문제가 발생할 수 있으므로 주의해서 다루어야 합니다.

먼저 함수에 반복 코드를 배치하세요.

# 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

(깨끗하고 매우 컴팩트한) 또 다른 대안은 명령을 배열로 로드한 다음 함수 포인터와 같은 $# 값을 통해 액세스하는 것입니다. 위의 함수를 고려하면 is_fileBash 코드는 다음과 같습니다.

# 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에 쓸 때 추가된 메모는 파일이 존재하지 않거나 덮어쓰여지는지 확인해야 합니다.

관련 정보