检查 arg 是否是文件并相应地打印

检查 arg 是否是文件并相应地打印

我有以下代码,通过 arg 接收文件,如果不存在,则从 stdin 获取:

file=
column=
pattern=
path=
cmd="echo"

while getopts "f:c:e:" opt; do
        case $opt in
                f) file="$OPTARG";;
                c) column="$OPTARG";;
                e) pattern="$OPTARG";;
                *) echo "Usage: $0 -f [file] -c [column] -e [pattern]"
        esac
done

if [ -z "$file" ]; then
        file=$(cat)
fi

这种方法的问题是, cat 不适用于非文件,因此如果它是从 stdout 获取的,例如:

cat data.csv | ./read.sh -c 2 -e " Max"

那么我将无法运行:

cat "$file"

在 bash 脚本中,我必须使用 echo 来代替。

OTOH,如果它是一个文件,我可以使用 cat 并且一切都很好。我的问题是如何使脚本能够识别 var $file 是否是实际的文件名或标准输出。

编辑:我已经考虑了评论和答案,这是我如何向用户读取他们指定的列和正则表达式的整个脚本,并考虑它们从标准输入或作为参数传递的文件:

#!/bin/bash

unset -v column pattern filepath
cmd=echo

DELIMITER=","

while getopts "f:c:e:" opt; do
        case $opt in
                f) file="$OPTARG";;
                c) column="$OPTARG";;
                e) pattern="$OPTARG";;
                *) printf>&2 '%s\n' "Usag: $0 [-f file] [-c column] [-e pattern]"; exit 1
        esac
done

if [ -z "$file" ]; then
        file=$(cat)
elif [ -z "$column" ]; then
        echo "column number neeeded!"
elif [ -z "$pattern" ]; then
        echo "pattern needed!"
fi

if [ -f "$file" ]; then
        cmd="cat"
fi

if [ "$column" -eq 5 ]; then
        DELIMITER=":"
fi

"$cmd" "$file" | awk -v col="$column" -v pattern="$pattern" -v del="$DELIMITER" -F "$DELIMITER" '{
        for (i=1;i<=NF;i++) {
                if ( i == col && $i == pattern ) {
                        print $0
                } else if ( del == ":" && $i == pattern ) {
                        print $0
                }
        }       
}'

exit 0

它有效,但我确信这不是正确的方法。我再次尝试让我的文件同时执行以下两项操作:

cat data.csv | ./read.sh -c 2 -e "Max"

和:

./read.sh -f data.csv -c 2 -e "Max"

上面的脚本可以工作,但优化是游戏的名称!

答案1

对于cat-表示 stdin。在许多系统上,/dev/stdin它可以与任何命令一起使用,而不仅仅是cat,而在 Linux 或 Cygwin 上,/dev/stdin它将指向与 stdin 上打开的相同文件,在某些情况下,其行为与 stdin 并不完全相同。

所以你可以这样做:

file=-
unset -v column pattern filepath
cmd=echo

while getopts f:c:e: opt; do
  case $opt in
    (f) file="$OPTARG";;
    (c) column="$OPTARG";;
    (e) pattern="$OPTARG";;
    (*) printf>&2 '%s\n' "Usage: $0 [-f file] [-c column] [-e pattern]"; exit 1
  esac
done
shift "$(( OPTIND - 1 ))"

cat -- "$file"

请注意,您只能读取 stdin ( -) 一次。

另请注意:

  • 错误应该发送到 stderr
  • echo不能用于任意数据(例如$0您无法控制的数据)。
  • [...]按约定方式使用消息选修的,所以对于-f [file],您暗示-f是必需的,但它的参数是可选的,这在这里不正确。
  • path如果该脚本可能最终在zsh(不在sh仿真中时)被解释,请避免命名变量,$path其中数组变量$PATHcsh/类似tcsh
  • 您应该避免测试空值来检查是否提供了参数。例如,使用 时,即使用户显式传递了一个参数(诚然,此处的文件路径是假的)-f '',您也会得到一个空值。相反,在初始化中使用以确保变量未设置,并检查以检查之后是否仍未设置。或者使用额外的布尔标志变量来记录选项是否通过。$file-funset -v var[ -z "${var+set}" ]
  • 如果您想允许用户传递一个名为 的文件,并且意味着在当前工作目录中-调用的实际文件,而不是 stdin,您可以将其转换为处理.-./-(f)
  • file=$(cat)读取可以从 stdin 读取的内容并将其存储在file变量中(在内存中)。因此,正如其他人所说,变量的名称有点误导,因为那是您获得的输入文件的内容。但它也是碾压你得到的内容会$(...)删除所有尾随的换行符,除了 zsh 之外,会删除或阻塞 NUL 字符。如果您echo在其上使用(同样不能用于任意数据),echo则会自行进行损坏。

在这里,您还可以采取相反的方法并打开作为参数传递给-fstdin 的文件:

unset -v column pattern filepath
cmd=echo

while getopts f:c:e: opt; do
  case $opt in
    (f) exec < "$OPTARG";;
    (c) column="$OPTARG";;
    (e) pattern="$OPTARG";;
    (*) printf>&2 '%s\n' "Usage: $0 [-f file] [-c column] [-e pattern]"; exit 1
  esac
done
shift "$(( OPTIND - 1 ))"

cat

exec请注意,当无法打开文件时,POSIX shell 将自动退出。如果传递了多个-f选项,例如分配 时$file,则仅考虑最后一个选项。

答案2

要回答您的实际问题,您可以使用test查看它是否是一个文件。所以

if [ -z "$file" ] ; then
    echo no file specified
elif [ -f "$file" ] ; then
    echo given a valid file "$file"
else
    echo Given "$file" but it is not a file
fi

然而我认为这没有抓住要点,你已经file=$(cat)并且我认为你对变量名的重用感到困惑file。如果你这么说file_contents=$(cat),我想你会发现事情更清楚。

相关内容