2016-08-01 2 views
2

Я пишу сценарий для резервного копирования, как это:Как передать переменное количество параметров команде оболочки?

backup.sh:

dir="$1" 
mode="$2" 
delta="$3" 

for file in "$dir/backup."*".$mode.tar.gz"; do 
    [ "$file" -nt "$ref" ] && ref="$file" 
done 

if [ "$delta" = "true" ]; then 
    delta_cmd=-N "'$ref'" 
fi 

backup_file="$dir/backup.$(date +%Y%m%d-%H%M%S).$mode.tar.gz" 

case "$mode" in 
    config) 
     tar -cpzvf "$backup_file" $delta_cmd \ 
      /etc \ 
      /usr/local 
      ;; 
    # still other modes here... 
esac 

Я хочу передать один переменный $delta_cmd в команду дегтя, так что гудроны все файлы или только дельта файлы с момента последнего резервного копирования в зависимости от значения $delta.

Приведенный выше код создает сообщение об ошибке и не деформирует дельта-файлы правильно, если для параметра $delta установлено значение true. Как это исправить?

P.S: Сценарий лучше совместим с POSIX.

+1

Это * возможно * сделать этот POSIX совместимым, но только путем внедрения уязвимостей безопасности (с использованием 'eval') или перезаписывания' $ @ '(что вполне * не * совместимо с использованием строки' delta_cmd'), что означает инкапсуляцию t он код в функции, если вы не хотите переопределять глобальное значение сценария для этого массива. См. BashFAQ # 50 для описания того, почему строковые переменные (в отличие от массивов) нельзя безопасно использовать для хранения списков или команд аргументов по адресу http://mywiki.wooledge.org/BashFAQ/050 –

+0

Не могли бы вы немного объяснить больше о переписывании '$ @' способом? –

+1

Добавлен ответ. Кстати, в этом коде есть много чего, что можно было бы сократить, чтобы создать правильный MCVE (** минимальный **, полный, проверенный пример, см. Http://stackoverflow.com/help/mcve) - I ' я очень сомневаюсь включить контент в свой ответ, который включает в себя такие плохие практики, как синтаксический анализ 'ls', но замена лучшей практики для этого кода также несколько выходит за рамки рассматриваемого вопроса. –

ответ

1

В POSIX-совместимый подход, рассмотрим:

set --     # clear [email protected] 
if [ -f "$ref" ]; then 
    set -- "[email protected]" -N "$ref" # add -N "$ref" to [email protected] 
fi 

tar ... "[email protected]" ...  # expand [email protected] into command line 

Чтобы положить все это в контексте, и защитить главный список аргументов против перезаписи, может выглядеть следующим образом:

#!/bin/sh 

main() { 
    # if current shell supports "local", prevent variables from leaking 
    # ...some "POSIX" shells, such as ash, will be fine with this. 
    local dir mode delta target_file backup_file 2>&1 ||: 

    dir=$1 
    mode=$2 
    delta=$3 

    set -- # clear [email protected] 

    for file in "$dir/backup."*".$mode.tar.gz"; do 
     [ "$file" -nt "$ref" ] && ref="$file" 
    done 

    if [ "$delta" = "true" ]; then 
     set -- "[email protected]" -N "$ref" 
    fi 

    target_file="$dir/backup.$(date +%Y%m%d-%H%M%S).$mode.tar.gz" 

    case "$mode" in 
     config) 
      tar -cpzvf "$target_file" "[email protected]" \ 
       /etc \ 
       /usr/local 
      ;; 
     # still other modes here... 
    esac 
} 

main "[email protected]" 
+0

Спасибо. Это полезно. Если мы поместим фрагмент кода в функцию в под-оболочке, такую ​​как 'backup() (...)', она не перезапишет $ @, не так ли? –

+1

правильный, он будет только перезаписывать '$ @' для этой функции. Вам действительно не нужно 'backup() (...)', 'backup() {...}' было бы достаточно, поскольку функция находится в другом стеке стека и имеет собственный список аргументов. –

+0

Спасибо. Это работает хорошо. –

1

Вы должны использовать BASH массивы для хранения частичной/полной командной строки:

#!/bin/bash 

DIR=/home/sysop/backup 
mode=main 
delta=false 

REF=$(ls -t "$DIR"/system.*.$mode.tar.gz "$DIR"/system.*.$mode-delta.tar.gz 2>/dev/null | head -n 1) 
REF=$(readlink -f "$REF") 

if [ "$delta" = true ]; then 
    delta_cmd=(-N "$REF") 
    delta_suffix=("-delta") 
fi 

target_file="$DIR/system.$(date +%Y%m%d-%H%M%S).$mode$delta_suffix.tar.gz" 

tar -cpzvf "$target_file" "${delta_cmd[@]}" \ 
    /etc \ 
    /usr/local \ 
    /var/log \ 
    /var/spool \ 
    /home/*/logs 

Я хотел бы также предложить избежать разбора вывода ls командования в сценарии.

+0

Он не работает POSIX-ly из-за ошибки синтаксиса. Я также попытался использовать bash, но результатом является ошибка, как раньше, если $ delta = true. –

+0

Этот скрипт должен работать на BASH. Можете ли вы сказать мне, что такое точная ошибка при использовании BASH? – anubhava

+0

В моем реальном скрипте используется другой путь для $ DIR, который потенциально может содержать пробел. Вот ошибка: tar: Подстановка -9223372036854775807 для неизвестного формата даты ''/path/to/subdir ' tar: after-space/system.20160802-004650.main-delta.tar.gz': Can not stat: No такой файл или каталог –