在 shell 脚本中使用 shift 的目的是什么?

Pat*_*ryk 173 shell-script arguments

我遇到了这个脚本:

#! /bin/bash                                                                                                                                                                                           

if (( $# < 3 )); then
  echo "$0 old_string new_string file [file...]"
  exit 0
else
  ostr="$1"; shift
  nstr="$1"; shift  
fi

echo "Replacing \"$ostr\" with \"$nstr\""
for file in $@; do
  if [ -f $file ]; then
    echo "Working with: $file"
    eval "sed 's/"$ostr"/"$nstr"/g' $file" > $file.tmp 
    mv $file.tmp $file
  fi  
done
Run Code Online (Sandbox Code Playgroud)

他们使用的行的含义是什么shift?我认为脚本应该至少与参数一起使用,所以......?

hum*_*ace 202

shift是一个bash内置的,它从参数列表的开头删除参数。鉴于提供给脚本的 3 个参数在$1, $2,中可用$3,那么调用shift$2生成新的$1. Ashift 2将移动两个使$1新旧$3。有关更多信息,请参见此处:

  • +1 请注意,这不是*旋转*它们,而是*将*它们从(参数)数组中移出。Shift/unshift 和 pop/push 是这种一般操作的通用名称(参见,例如,bash 的 `pushd` 和 `popd`)。 (35认同)

Sco*_*ott 47

正如金发姑娘的评论和人类的参考所描述的那样, shift重新分配位置参数($1$2等),以便$1采用 的旧值$2$2采用 的值$3等。*  的旧值$1被丢弃。($0未更改。)这样做的一些原因包括:

  • 它使您可以更轻松地访问第十个参数(如果有)。  $10不起作用 - 它被解释为$1与 a 连接0 (因此可能会产生类似Hello0)。在 a 之后shift,第十个参数变为$9。(但是,在大多数现代 shell 中,您可以使用${10}.)
  • 正如Bash Guide for Beginners 所展示的那样,它可用于遍历参数。IMNSHO,这很笨拙;for对此要好得多。
  • 就像在您的示例脚本中一样,除了少数几个参数之外,它可以轻松地以相同的方式处理所有参数。例如,在您的脚本中, $1$2是文本字符串,而$3和所有其他参数是文件名。

所以这就是它的表现。假设你的脚本被调用Patryk_script,它被称为

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3
Run Code Online (Sandbox Code Playgroud)

脚本看到

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3
Run Code Online (Sandbox Code Playgroud)

该语句ostr="$1"将变量设置ostrUSSR。第一条shift语句更改位置参数如下:

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3
Run Code Online (Sandbox Code Playgroud)

该语句nstr="$1"将变量设置nstrRussia。第二个shift语句更改位置参数如下:

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3
Run Code Online (Sandbox Code Playgroud)

然后for循环将文件, , 和中的USSR( $ostr) 变为Russia( $nstr) 。Treaty1Atlas2Pravda3



脚本存在一些问题。

  1. 用于 $@ 中的文件;做

    如果脚本被调用为

    Patryk_script 苏联俄罗斯条约1“世界地图集2”真理报3

    它看到

    1 美元 = 苏联
    2 美元 = 俄罗斯
    3 美元 = 条约 1
    4 美元 = 世界地图集 2
    5 美元 = 真理报 3

    但是,因为$@没有被引用,所以空格World Atlas2没有被引用,for循环认为它有四个文件:Treaty1, World, Atlas2, 和Pravda3. 这应该是

    对于“$@”中的文件;做

    (引用参数中的任何特殊字符)或简单地

    为文件做

    (相当于更长的版本)。

  2. eval "sed 's/"$ostr"/"$nstr"/g' $file"

    不需要将其设为eval,并且将未经检查的用户输入传递给eval可能是危险的。例如,如果脚本被调用为

    Patryk_script "'; rm *;'" 俄罗斯条约1 Atlas2 Pravda3

    它会执行rm *!如果脚本可以以高于调用它的用户的权限运行,这是一个大问题;例如,如果它可以通过sudoWeb 界面运行或调用。如果您只是在您的目录中将其用作自己,那么这可能并不那么重要。但是可以改成

    sed "s/$ostr/$nstr/g" "$file"

    这仍然存在一些风险,但风险要小得多。

  3. if [ -f $file ],> $file.tmpmv $file.tmp $file 应该分别是if [ -f "$file" ],> "$file.tmp"mv "$file.tmp" "$file",以处理其中可能包含空格(或其他有趣字符)的文件名。(该eval "sed …命令还会对包含空格的文件名进行处理。)


* shift接受一个可选参数:一个正整数,指定要移动的参数数量。默认值为一 ( 1)。例如,shift 4导致$5 成为$1$6成为$2、等等。(请注意,Bash 初学者指南中的示例是错误的。)因此您的脚本可以修改为

for file in $@; do

这可能被认为更清楚。



尾注/警告:

Windows 命令提示符(批处理文件)语言也支持一个SHIFT命令,它的作用与shiftUnix shell 中的命令基本相同,但有一个显着区别,我将隐藏它以防止人们被它混淆:

  • 类似的命令SHIFT 4是一个错误,产生“无效的 SHIFT 命令参数”错误消息。
  • SHIFT /n,其中n是 0 到 8 之间的整数,是有效的——但它不会移动时间。它移动一次,n个参数开始。所以 导致(第五个参数)变成变成,依此类推,只留下参数 0 到 3。n SHIFT /4%5%4,  %6%5

  • `shift` 可以应用于 `while`、`until` 甚至 `for` 循环,以比简单的 `for` 循环更微妙的方式处理数组。我经常发现它在 `for` 循环中很有用,比如...... `set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" &amp;&amp; fn_that_wants_split_arg "$arg"; 完成` (2认同)

eig*_*eld 5

最简单的解释是这样的。考虑以下命令:

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 
Run Code Online (Sandbox Code Playgroud)

如果没有帮助,shift您将无法提取位置的完整值,因为该值可能会变得任意长。当您移动两次时,argsSETlocation会被删除,这样:

x="$@"
echo "location = $x"
Run Code Online (Sandbox Code Playgroud)

长时间盯着那个$@东西。这意味着,它可以将完整的位置保存到变量中x,尽管它有空格和逗号。总之,我们调用shift,然后检索变量中剩下的值$@

更新 我在下面添加了一个非常短的片段,显示了它的概念和用途shift,如果没有它,正确提取字段将非常非常困难。

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b
Run Code Online (Sandbox Code Playgroud)

解释:在 后shift,变量b应包含传入的其余内容,无论空格等。这[ -n "$b" ] && echo $b是一种保护,这样我们仅在 b有内容时才打印b 。