Какова цель использования 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? Я предполагаю, что сценарий должен использоваться по крайней мере с аргументами, так что ...?

Patryk
источник

Ответы:

141

shiftявляется bashвстроенным, который удаляет аргументы из начала списка аргументов. Учитывая , что 3 аргументы, переданные сценарию доступны $1, $2, $3, то вызов shift будет сделать $2новый $1. А shift 2сдвинется на два, сделав новый $1старый $3. Для получения дополнительной информации см. Здесь:

humanityANDpeace
источник
25
+1 Обратите внимание, что они не вращаются , а смещают их из массива (аргументов). Shift / unshift и pop / push являются общими именами для этого общего вида манипуляций (см., Например, bash's pushdи popd).
Златовласка
1
Окончательная ссылка: gnu.org/software/bash/manual/bashref.html#index-shift
Гленн Джекман
@Goldilocks очень верно. Вместо «поворота» я должен был бы найти способ использовать другое слово, что-то вроде «переместить аргументы вперед в переменных аргумента». В любом случае, я надеюсь, что примеры в тексте и ссылка на ссылку все же прояснили.
человечествоANDpeace
Хотя в сценарии упоминается bash, вопрос является общим для всех оболочек, поэтому стандарт Posix превалирует: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
calandoa
32

Как комментарий Златовласка и ссылки человечества описывают, shiftпереназначение позиционных параметров ( $1, $2и т.д.) , так что $1берет на старое значение $2, $2принимает значения $3, и т.д. *   Старое значение $1отбрасывается. ( $0не изменено.) Некоторые причины для этого включают в себя:

  • Это позволяет вам легче получить доступ к десятому аргументу (если он есть).  $10не работает - он интерпретируется как $1сцепленный с 0 (и может привести к чему-то вроде Hello0). После shift, десятый аргумент становится $9. (Тем не менее, в большинстве современных оболочек вы можете использовать ${10}.)
  • Как демонстрирует Bash Guide для начинающих , его можно использовать для циклического прохождения аргументов. ИМНШО, это неуклюже; forнамного лучше для этого.
  • Как и в вашем примере сценария, он позволяет легко обрабатывать все аргументы одинаково, за исключением нескольких. Например, в вашем скрипте есть $1и $2текстовые строки, тогда как $3и все остальные параметры являются именами файлов.

Вот как это закончится. Предположим, ваш скрипт называется Patryk_scriptи называется

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

Скрипт видит

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

Оператор ostr="$1"устанавливает переменную ostrв USSR. Первый shiftоператор изменяет позиционные параметры следующим образом:

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

Оператор nstr="$1"устанавливает переменную nstrв Russia. Второй shiftоператор изменяет позиционные параметры следующим образом:

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

И тогда forизменения цикла USSR( $ostr) в Russia( $nstr) в файлах Treaty1, Atlas2и Pravda3.



Есть несколько проблем со сценарием.

  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 Атлас2 Правда3

    это выполнит rm *! Это большая проблема, если скрипт может быть запущен с привилегиями выше, чем у пользователя, который его вызывает; например, если он может быть запущен через sudoили вызван из веб-интерфейса. Вероятно, это не так важно, если вы просто используете его в своем каталоге. Но это можно изменить на

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

    Это все еще имеет некоторые риски, но они гораздо менее серьезны.

  3. if [ -f $file ], > $file.tmpи mv $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, с одним поразительным отличием, которое я скрою, чтобы не допустить путаницы:

  • Подобная команда SHIFT 4является ошибкой, приводящей к сообщению об ошибке «Недопустимый параметр для команды SHIFT».
  • SHIFT /nгде nцелое число от 0 до 8, допустимо, но оно не сдвигает время . Это сдвигает один раз, начиная с с п -  го аргумента. Таким образом , причины (пятый аргумент) , чтобы стать стать , и так далее, в результате чего аргументы 0 до 3 в одиночку.n SHIFT /4%5%4,  %6%5

Скотт
источник
2
shiftможет быть применен к while, untilи даже 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
3

Самое простое объяснение это. Рассмотрим команду:

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

Без посторонней помощи shiftвы не можете извлечь полное значение местоположения, поскольку это значение может быть произвольно длинным. Когда вы сдвигаетесь два раза, арги 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, только если у нее есть содержимое.

typelogic
источник
(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
Алгебра
источник