脚本问题中的bash参数变量

时间:2011-04-27 19:57:08

标签: bash variables

我有一个脚本,我写的是切换到root或以root身份运行命令而没有密码。我编辑了我的/ etc / sudoers文件,以便我的用户[matt]有权运行/ bin / su而没有密码。这是我的脚本“s”内容:

matt: ~ $ cat ~/bin/s
#!/bin/bash

[ "$1" != "" ] && c='-c'

sudo su $c "$*"

如果没有参数[只是s],它基本上会调用sudo su,它会在没有密码的情况下转到root。但是如果我把paramaters,$ c变量等于“-c”,这使得su执行一个命令。

除非我需要使用空格,否则效果很好。例如:

matt: ~ $ touch file\ with\ spaces
matt: ~ $ s chown matt file\ with\ spaces 
chown: cannot access 'file': No such file or directory
chown: cannot access 'with': No such file or directory
chown: cannot access 'spaces': No such file or directory
matt: ~ $ s chown matt 'file with spaces'
chown: cannot access 'file': No such file or directory
chown: cannot access 'with': No such file or directory
chown: cannot access 'spaces': No such file or directory
matt: ~ $ s chown matt 'file\ with\ spaces'
matt: ~ $ 

我该如何解决这个问题?

另外, $ * $ @ 之间有什么区别?

2 个答案:

答案 0 :(得分:8)

啊,引用很有趣。通常,@ John建议的方法适用于此:使用"$@",并且它不会尝试解释(并且混淆)参数中的空格和其他有趣的字符。但是,在这种情况下,这将无法工作,因为su的-c选项需要将整个命令作为单个参数传递,然后它将启动一个解析命令的新shell(包括被空格等混淆) 。为了避免这种情况,您实际上需要重新引用要传递给su -c的字符串中的参数。 bash的printf内置可以做到这一点:

#!/bin/bash

if [ $# -gt 0 ]; then
    sudo su -c "$(printf "%q " "$@")"
else
    sudo su
fi

让我回顾一下这里发生的事情:

  1. 您运行s chown matt file\ with\ spaces
  2. 之类的命令
  3. bash将其解析为单词列表:“s”“chown”“matt”“带空格的文件”。请注意,此时您输入的转义符已被删除(尽管它们具有预期效果:使bash将这些空格视为参数的一部分,而不是参数之间的分隔符。)
  4. 当bash在脚本中解析printf "%q " "$@"命令时,它会用脚本的参数替换"$@",参数分解完整。它相当于printf "%q " "chown" "matt" "file with spaces"
  5. printf将格式字符串“%q”解释为“以引用形式打印每个剩余参数,后面带一个空格”。它打印:“chown matt file \ with \ spaces”,基本上重建原始命令行(它最后有一个额外的空间,但事实证明这不是问题)。
  6. 然后将其作为参数传递给sudo(因为$()构造周围有双引号,它将被视为sudo的单个参数)。这相当于运行sudo su -c "chown matt file\ with\ spaces "
  7. sudo运行su,并传递其获得的参数列表的其余部分,包括完全转义的命令。
  8. su运行一个shell,它还会传递其参数列表的其余部分。
  9. shell执行它作为-cchown matt file\ with\ spaces的参数获得的命令。在解析它的正常过程中,它会将非转义空间解释为参数之间的分隔符,并将转义空格解释为参数的一部分,并且它将忽略末尾的额外空间。
  10. shell运行chown,参数“matt”和“file with spaces”。这符合您的期望。
  11. 不是bash解析hoot吗?

答案 1 :(得分:3)

"$*"将所有位置参数($1$2,...)收集到一个单词中,用一个空格分隔(更一般地说,$IFS的第一个字符)。请注意,在shell术语中,单词可以包含任何字符,包括空格:"foo bar"foo\ bar解析为单个单词。例如,如果有三个参数,则"$*"等同于"$1 $2 $3"。如果没有参数,那么"$*"相当于""(一个空字)。

"$@"是一种特殊的语法,可以扩展到位置参数列表,每个参数都在自己的单词中。例如,如果有三个参数,则"$@"等同于"$1" "$2" "$3"。如果没有参数,那么"$@"等同于 nothing (一个空列表,而不是一个单词为空的列表)。

"$@"几乎总是您要使用的内容,而不是"$*"或不加引号$*$@(后两个完全等效并执行文件名生成) (也称为globbing)和所有位置参数上的单词分割。)

还有一个问题,就是su,除了一个shell命令作为-c的参数,并且你传递了多个单词。你已经有了a detailed explanation of getting the quoting right,但是让我补充一下如何做正确的建议,这可以避免双重引用问题。您还可以参考https://unix.stackexchange.com/questions/3063/how-do-i-run-a-command-as-the-system-administrator-root了解sudosu的更多背景信息。

sudo已经以root身份运行命令,因此无需调用su。如果您的脚本没有参数,您可以直接运行shell;除非您的sudo版本很旧,否则可以选择:sudo -s。所以你的脚本可以是:

#!/bin/sh
if [ $# -eq 0 ]; then set -- -s; else set -- -- "$@"; fi
exec sudo "$@"

(其他部分是处理以-开头的命令的罕见情况。)

我不会打扰这么简短的剧本。以root身份运行命令是不寻常的,并且有足够的风险,输入三个额外的字符应该不是问题。以root身份运行shell更加不寻常和冒险,当然还需要六个字符。