我有以下代码,通过 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
其中数组变量$PATH
与csh
/类似tcsh
。- 您应该避免测试空值来检查是否提供了参数。例如,使用 时,即使用户显式传递了一个参数(诚然,此处的文件路径是假的)
-f ''
,您也会得到一个空值。相反,在初始化中使用以确保变量未设置,并检查以检查之后是否仍未设置。或者使用额外的布尔标志变量来记录选项是否通过。$file
-f
unset -v var
[ -z "${var+set}" ]
- 如果您想允许用户传递一个名为 的文件,并且意味着在当前工作目录中
-
调用的实际文件,而不是 stdin,您可以将其转换为处理.-
./-
(f)
file=$(cat)
读取可以从 stdin 读取的内容并将其存储在file
变量中(在内存中)。因此,正如其他人所说,变量的名称有点误导,因为那是您获得的输入文件的内容。但它也是碾压你得到的内容会$(...)
删除所有尾随的换行符,除了 zsh 之外,会删除或阻塞 NUL 字符。如果您echo
在其上使用(同样不能用于任意数据),echo
则会自行进行损坏。
在这里,您还可以采取相反的方法并打开作为参数传递给-f
stdin 的文件:
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)
,我想你会发现事情更清楚。