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


118

我遇到了这个脚本:

#! /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

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

Answers:


141

shiftbash内置的,可以从参数列表的开头删除参数。由于提供给脚本的3个参数是可用的$1$2$3,然后调用shift 将使$2$1。A shift 2会变2使新$1的变老$3。有关更多信息,请参见此处:


25
+1请注意,它没有旋转它们,而是它们移出了(参数的)数组。Shift / unshift和pop / push是这种一般操作的常用名称(例如,参见bash pushdpopd)。
goldilocks 2014年


@goldilocks非常正确。与其“旋转”,不如我应该更好地找到一种使用另一个单词的方法,例如“将参数在参数变量中前进”。无论如何,我希望本文中的示例和参考文献的链接能够使它们变得清楚。
humanityANDpeace

虽然剧本中提到bash的问题是,一般所有的炮弹,因此POSIX标准为准:pubs.opengroup.org/onlinepubs/9699919799/utilities/...
calandoa

32

正如goldilocks的评论和人类参考文献所描述的那样, shift重新分配位置参数($1$2等),以便$1采用的旧值$2$2采用的值$3等。*$1丢弃  旧的值。($0未更改。)执行此操作的某些原因包括:

  • 它使您可以更轻松地访问第十个参数(如果有)。  $10不起作用–它被解释为与$1串联0 (因此可能会产生Hello0)。在a之后shift,第十个参数变为$9。(但是,在大多数现代Shell中,您可以使用${10}。)
  • 正如Bash初学者指南所演示的那样,它可用于循环遍历参数。恕我直言,这很笨拙;for对此要好得多。
  • 就像您的示例脚本一样,它使您可以轻松地以相同的方式处理所有参数,除了少数几个。例如,在脚本中, $1$2是文本字符串,而$3and所有其他参数是文件名。

这就是它的播放方式。假设您的脚本被调用Patryk_script,并且被称为

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

脚本看到

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3

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

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3

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

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3

然后for循环变化USSR$ostr)来Russia$nstr在文件中)Treaty1Atlas2Pravda3



脚本存在一些问题。

  1. 以$ @归档;做

    如果脚本被调用为

    Patryk_script苏联俄罗斯条约1“世界地图集2” Pravda3

    它看到

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

    但是,因为$@没有加引号,在太空World Atlas2中没有报价,并且for循环认为它有四个文件:Treaty1WorldAtlas2,和Pravda3。这应该是

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

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

    对于文件

    (相当于更长的版本)。

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

    不必将其设置为eval,并且将未经检查的用户输入传递给eval会很危险。例如,如果脚本被调用为

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

    它会执行rm *!如果脚本可以比调用它的用户更高的特权运行,这将是一个大问题。例如,它是否可以通过sudoWeb界面运行或从Web界面调用。如果仅在目录中以自己的方式使用它,那么可能就没有那么重要了。但是可以更改为

    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初学者指南》中的示例是错误的。)因此,可以将脚本修改为:

ostr="$1"
nstr="$2"
shift 2

可能会更清楚。



结束语 /警告:

Windows命令提示符(批处理文件)语言还支持SHIFT命令,该shift命令与Unix shell中的命令基本上具有相同的功能,但有一个显着的不同,我将隐藏该代码,以防止人们对此感到困惑:

  • 像这样的命令SHIFT 4是错误,产生“对SHIFT命令无效的参数”错误消息。
  • SHIFT /n,其中n为0到8之间的整数是有效的-但它不会移位times。它n  个参数开始偏移一次。因此, 导致(第五个参数)变为,依此类推,仅将参数0到3保留下来。n SHIFT /4%5%4,  %6%5


2
shift可以应用于whileuntil甚至for循环,以比简单for循环更微妙的方式处理数组。我经常发现它在for类似... 这样的循环中 很有用set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" && fn_that_wants_split_arg "$arg"; done
mikeserv 2014年

3

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

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 

没有shift您的帮助,您将无法提取位置的完整值,因为该值可能会任意长。当您移动两次时,args SET和将location被删除,从而:

x="$@"
echo "location = $x"

凝视着那$@东西。这意味着,x尽管有空格和逗号,它仍可以将完整位置保存到变量中。因此,总而言之,我们先调用shift,然后再检索变量中剩余的值$@

更新 我在下面添加了一个非常简短的代码段,显示了shift没有此代码段的概念和实用性,要正确提取字段非常非常困难。

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b

说明:以后shift,变量b应包含剩下的东西被传递,没有空格物质或等[ -n "$b" ] && echo $b是保护这样的,我们只打印b,如果它有一个内容。


(1)这不是最简单的解释。这是一个有趣的角度。(2)但是,只要您要串联一堆参数(或所有参数),就可能要养成使用"$*"而不是的习惯"$@"。(3)我本来要对您的答案表示赞同,但我没有,因为您说的是“两次移位”,但实际上并没有在代码中显示出来。(4)如果您希望脚本能够从命令行获取多字值,最好要求用户将整个值放在引号中。…(续)
斯科特,

(续)…您今天可能不在乎,但是您的方法不允许您使用多个空格(Philippines   6014),前导空格,尾随空格或制表符。(5)顺便说一句,您也许还可以通过进行bash操作x="${*:3}"
斯科特,

我试图解释您的“ ***不允许多个空格***”注释的来源,因为这与shift命令无关。您可能是指$ @与$ *的细微差别。但是事实仍然存在,我上面的代码段可以准确地提取位置,无论尾随或前方有多少空间都没有关系。转移后,位置将包含正确的位置。
typelogic

2

shift 将命令行参数视为FIFO队列,每次调用时都会弹出popleft元素。

array = [a, b, c]
shift equivalent to
array.popleft
[b, c]
$1, $2,$3 can be interpreted as index of the array.
$# is the length of array
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.