问题描述
我在名为实例的文件中有一个脚本:
echo "hello world"
echo ${1}
当我使用以下命令运行此脚本时:
./instance solfish
我得到这个输出:
hello world
solfish
但是当我跑步时:
echo $#
上面写着 “0”。为什么?我不明白 $#
是什么意思。
请解释一下。
最佳思路
$#
是 bash
中的一个特殊变量,它扩展到参数(位置参数)的数量,即 $1, $2 ...
传递到相关脚本或 shell(如果参数直接传递到 shell),例如在 bash -c '...' ....
中。
这类似于C中的argc
。
也许这会清楚地表明:
$ bash -c 'echo $#'
0
$ bash -c 'echo $#' _ x
1
$ bash -c 'echo $#' _ x y
2
$ bash -c 'echo $#' _ x y z
3
请注意, bash -c
在其后面的命令之后从 0 开始接受参数( $0
;从技术上讲,这只是 bash
让您设置 $0
的方式,而不是真正的参数),因此 _
在这里仅用作占位符;实际参数为 x
( $1
)、y
( $2
) 和 z
( $3
)。
同样,在您的脚本中(假设 script.sh
)如果您有:
#!/usr/bin/env bash
echo "$#"
然后当你这样做时:
./script.sh foo bar
脚本将输出 2;同样地,
./script.sh foo
将输出1。
次佳思路
echo $#
输出脚本的位置参数的数量。
你没有,所以它输出 0。
echo $#
在脚本内部很有用,而不是作为单独的命令。
如果您运行带有某些参数的脚本,例如
./instance par1 par2
放入脚本中的 echo $#
将输出 2。
第三种思路
$#
通常在 bash 脚本中使用,以确保传递参数。通常,您在脚本的开头检查参数。
例如,这是我今天正在编写的脚本的片段:
if [[ $# -ne 1 ]]; then
echo 'One argument required for the file name, e.g. "Backup-2017-07-25"'
echo '.tar will automatically be added as a file extension'
exit 1
fi
总而言之,$#
报告传递给脚本的参数数量。在您的情况下,您没有传递任何参数,报告的结果是 0
。
#
在 Bash 中的其他用途
#
通常在 bash 中用于计算变量出现的次数或长度。
要查找字符串的长度:
myvar="some string"; echo ${#myvar}
返回:11
要查找数组元素的数量:
myArr=(A B C); echo ${#myArr[@]}
返回:3
要查找第一个数组元素的长度:
myArr=(A B C); echo ${#myArr[0]}
返回: 1
( A
的长度,0 是第一个元素,因为数组使用从零开始的索引/下标)。
第四种思路
$#
是参数的数量,但请记住它在函数中会有所不同。
$#
是传递给脚本、shell 或 shell 函数的位置参数的数量。这是因为,当 shell 函数运行时,positional parameters are temporarily replaced with the arguments to the function .这使得函数可以接受并使用它们自己的位置参数。
无论有多少参数传递给脚本本身,此脚本始终打印 3
,因为函数 f
中的 "$#"
扩展为传递给函数的参数数量:
#!/bin/sh
f() {
echo "$#"
}
f a b c
这很重要,因为如果您不熟悉位置参数在 shell 函数中的工作方式,则这意味着这样的代码将无法按您的预期工作:
#!/bin/sh
check_args() { # doesn't work!
if [ "$#" -ne 2 ]; then
printf '%s: error: need 2 arguments, got %d\n' "$0" "$#" >&2
exit 1
fi
}
# Maybe check some other things...
check_args
# Do other stuff...
在 check_args
中, $#
扩展为传递给函数本身的参数数量,在该脚本中始终为 0。
如果您想在 shell 函数中使用此类功能,则必须编写如下内容:
#!/bin/sh
check_args() { # works -- the caller must pass the number of arguments received
if [ "$1" -ne 2 ]; then
printf '%s: error: need 2 arguments, got %d\n' "$0" "$1" >&2
exit 1
fi
}
# Maybe check some other things...
check_args "$#"
这是可行的,因为 $#
在函数外部扩展并作为其位置参数之一传递给函数。在函数内部,$1
扩展为传递给 shell 函数的第一个位置参数,而不是传递给它所属的脚本。
因此,与 $#
一样,特殊参数 $1
、 $2
等,以及 $@
和 $*
,当它们在函数中扩展时,也属于传递给函数的参数。但是,$0
不会更改函数的名称,这就是为什么我仍然能够使用它来生成质量错误消息。
$ ./check-args-demo a b c
./check-args-demo: error: need 2 arguments, got 3
同样,如果您在另一个函数中定义一个函数,则您将使用传递到执行扩展的最里面函数的位置参数:
#!/bin/sh
outer() {
inner() {
printf 'inner() got %d arguments\n' "$#"
}
printf 'outer() got %d arguments\n' "$#"
inner x y z
}
printf 'script got %d arguments\n' "$#"
outer p q
我将此脚本称为 nested
并且(运行 chmod +x nested
后)我运行了它:
$ ./nested a
script got 1 arguments
outer() got 2 arguments
inner() got 3 arguments
是的,我知道。 “1 个参数”是一个复数错误。
位置参数也可以更改。
如果您正在编写脚本,则函数外部的位置参数将是传递给脚本的 命令行 参数,除非您更改了它们。
更改它们的一种常见方法是使用 shift
内置函数,它将每个位置参数向左移动一位,删除第一个参数并将 $#
减少 1:
#!/bin/sh
while [ "$#" -ne 0 ]; do
printf '%d argument(s) remaining.\nGot "%s".\n\n' "$#" "$1"
shift
done
$ ./do-shift foo bar baz # I named the script do-shift.
3 argument(s) remaining.
Got "foo".
2 argument(s) remaining.
Got "bar".
1 argument(s) remaining.
Got "baz".
它们也可以使用 set
内置函数进行更改:
#!/bin/sh
printf '%d args: %s\n' "$#" "$*"
set foo bar baz
printf '%d args: %s\n' "$#" "$*"
$ ./set-args a b c d e # I named the script set-args.
5 args: a b c d e
3 args: foo bar baz