退货的最佳做法是什么许多来自 bash 函数的值?
示例1:
函数脚本:
function mysqlquery {
local dbserver='localhost'
local dbuser='user'
local dbpass='pass'
local db='mydb'
mysql -h "$dbserver" -u "$dbuser" -p "$dbpass" --skip-column-names --raw -e "$*" "$db"
if [ $? -ne 0 ]; then
return 1
fi
}
源脚本:
for XY in $(mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null);do
dosomethingwith $XY
done
if mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null; then
echo true
fi
示例2:
函数脚本:
function mysqlquery {
local dbserver='localhost'
local dbuser='user'
local dbpass='pass'
local db='mydb'
result=$(mysql -h "$dbserver" -u "$dbuser" -p "$dbpass" -e "$*" "$db" 2>/dev/null)
if [ $? -ne 0 -o -z "$result" ]; then
return 1
fi
}
源脚本:
result=$(mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null)
for XY in $result;do
dosomethingwith $XY
done
if mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null; then
echo true
fi
或者是否有更多方法来返回多条信息(比单个 int 值多得多)?
答案1
是的,bash
只能return
返回数字,而且只能返回 0 到 255 之间的整数。
对于可以返回任何内容(事物列表)的 shell,您可以查看es
:
$ es -c "fn f {return (a 'b c' d \$*)}; printf '%s\n' <={f x y}"
a
b c
d
x
y
现在,在类似 Korn 的 shell 中bash
,您始终可以在预先商定的变量中返回数据。该变量可以是 shell 支持的任何类型。
对于bash
,它可以是标量稀疏数组(键仅限于正整数的关联数组)或具有非空键的关联数组(键和值都不能包含 NUL 字符)。
另请参阅zsh
没有这些限制的普通数组和关联数组。
与上述功能等效的功能f
es
可以通过以下方式完成:
f() {
reply=(a 'b c' d "$@")
}
f
printf '%s\n' "${reply[@]}"
现在,mysql
查询通常返回表,即二维数组。据我所知,唯一具有多维数组的 shell 是ksh93
(bash
尽管它不支持变量中的 NUL 字符)。
ksh
也支持化合物可以方便地返回带有标题的表的变量。
它还支持通过引用传递变量。
所以,你可以这样做:
function f {
typeset -n var=$1
var=(
(foo bar baz)
(1 2 3)
}
}
f reply
printf '%s\n' "${reply[0][1]}" "${reply[1][2]}"
或者:
function f {
typeset -n var=$1
var=(
(firstname=John lastname=Smith)
(firstname=Alice lastname=Doe)
)
}
f reply
printf '%s\n' "${reply[0].lastname}"
现在,为了获取输出mysql
并将其存储在某些变量中,我们需要解析该输出,该输出是文本,其中表的列由 TAB 字符分隔,行由 NL 分隔,并对值进行某种编码以允许它们同时包含 NL和选项卡。
如果没有--raw
,mysql
将输出 NL as \n
、TAB as \t
、反斜杠 as\\
和 NUL as \0
。
ksh93
还read -C
可以读取格式化为变量定义的文本(eval
虽然与 using 没有太大不同),所以你可以这样做:
function mysql_to_narray {
awk -F '\t' -v q="'" '
function quote(s) {
gsub(/\\n/, "\n", s)
gsub(/\\t/, "\t", s)
gsub(/\\\\/, "\\", s)
gsub(q, q "\\" q q, s)
return q s q
}
BEGIN{print "("}
{
print "("
for (i = 1; i <= NF; i++)
print " " quote($i)
print ")"
}
END {print ")"}'
}
function query {
typeset -n var=$1
typeset db=$2
shift 2
typeset -i n=0
typeset IFS=' '
typeset credentials=/path/to/file.my # not password on the command line!
set -o pipefail
mysql --defaults-extra-file="$credentials" --batch \
--skip-column-names -e "$*" "$db" |
mysql_to_narray |
read -C var
}
用作
query myvar mydb 'select * from mytable' || exit
printf '%s\n' "${myvar[0][0]}"...
或者对于复合变量:
function mysql_to_array_of_compounds {
awk -F '\t' -v q="'" '
function quote(s) {
gsub(/\\n/, "\n", s)
gsub(/\\t/, "\t", s)
gsub(/\\\\/, "\\", s)
gsub(q, q "\\" q q, s)
return q s q
}
BEGIN{print "("}
NR == 1 {
for (i = 1; i<= NF; i++) header[i] = $i
next
}
{
print "("
for (i = 1; i <= NF; i++)
print " " header[i] "=" quote($i)
print ")"
}
END {print ")"}'
}
function query {
typeset -n var=$1
typeset db=$2
shift 2
typeset -i n=0
typeset IFS=' '
typeset credentials=/path/to/file.my # not password on the command line!
set -o pipefail
mysql --defaults-extra-file="$credentials" --batch \
-e "$*" "$db" |
mysql_to_array_of_compounds |
read -C var
}
用作:
query myvar mydb 'select "First Name" as firstname,
"Last Name" as lastname from mytable' || exit
printf '%s\n' "${myvar[0].firstname"
请注意,标头名称(firstname
上面lastname
的 )必须是有效的 shell 标识符。
在bash
or zsh
or中yash
(尽管在 zsh 和 yash 中数组索引从 1 开始并且只能zsh
存储 NUL 字符),您始终可以通过awk
生成代码来定义它们,从而每列返回一个数组:
query() {
typeset db="$1"
shift
typeset IFS=' '
typeset credentials=/path/to/file.my # not password on the command line!
set -o pipefail
typeset output
output=$(
mysql --defaults-extra-file="$credentials" --batch \
-e "$*" "$db" |
awk -F '\t' -v q="'" '
function quote(s) {
gsub(/\\n/, "\n", s)
gsub(/\\t/, "\t", s)
gsub(/\\\\/, "\\", s)
gsub(q, q "\\" q q, s)
return q s q
}
NR == 1 {
for (n = 1; n<= NF; n++) column[n] = $n "=("
next
}
{
for (i = 1; i < n; i++)
column[i] = column[i] " " quote($i)
}
END {
for (i = 1; i < n; i++)
print column[i] ") "
}'
) || return
eval "$output"
}
用作:
query mydb 'select "First Name" as firstname,
"Last Name" as lastname from mytable' || exit
printf '%s\n' "${firstname[1]}"
在 之前添加set -o localoptions
withzsh
或with bash4.4+以便将该选项设置为本地函数,就像方法一样。local -
set -o pipefail
ksh93
请注意,在上述所有内容中,我们不会将\0
s 转换回真正的 NUL,因为bash
orksh93
会被它们阻塞。如果您希望zsh
能够使用 BLOB,则可能需要这样做,但请注意,这gsub(/\\0/, "\0", s)
并不适用于所有awk
实现。
无论如何,在这里,我会使用比 shell 更高级的语言(如 perl 或 python)来完成这种事情。
答案2
好吧,这取决于您想要/需要哪种输出格式。最简单的方法可能是只打印函数的输出,这样函数的行为就像任何其他命令一样。另一种方法是从函数内部设置一些变量(可能是关联数组)。这样做的好处是不同的项目可以干净地分开,但您可能需要对一些变量进行硬编码。
第一个示例中的函数实现了前者:无论 mysql 客户端从该函数打印什么内容,都会转到该函数的标准输出。鉴于数据已经以字节流的形式出现,保持原样就可以了。
但在这里,问题变成了如何处理输出。for x in $(somecmd) ...
不好,因为 的输出somecmd
被分割为单词并针对文件名全局进行处理。通常使用它会更好while read ...
,请参阅如何逐行(和/或逐字段)读取文件(数据流、变量)?
mysql
要逐行读取输出,您可以这样做
mysql -h "$dbserver" etc. etc. | while read -r line ; do
dosomethingwith "$line"
done
或者用一个函数
mysqlquery() {
...
mysql -h "$dbserver" etc. etc. 2>/dev/null
}
mysqlquery | while read -r line ; do ...
请注意,您不需要if [ $? -ne 0 ]; then return 1
:函数的返回值与最后一个命令的返回值相同。如果您使用该函数为管道提供数据,那么查看返回值并不容易。