Подтвердить что ты не робот

Переменные как команды в сценариях bash

Я пишу очень простой bash script, который записывает заданный каталог, шифрует его вывод и разбивает результирующий файл на несколько меньших файлов, поскольку носители резервного копирования не поддерживают огромные файлы.

У меня нет большого опыта работы с bash скриптами. Я считаю, что у меня проблемы с цитированием моих переменных, чтобы разрешить пробелы в параметрах. script следует:

#! /bin/bash

# This script tars the given directory, encrypts it, and transfers
# it to the given directory (likely a USB key).

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

DIRECTORY=$1
BACKUP_DIRECTORY=$2
BACKUP_FILE="$BACKUP_DIRECTORY/`date +%Y-%m-%dT%H-%M-%S.backup`"

TAR_CMD="tar cv $DIRECTORY"
SPLIT_CMD="split -b 1024m - \"$BACKUP_FILE\""

ENCRYPT_CMD='openssl des3 -salt'

echo "$TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD"

$TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

say "Done backing up"

Выполнение этой команды завершается с:

split: "foo/2009-04-27T14-32-04.backup" aa: Нет такого файла или каталога

Я могу исправить это, удалив цитаты вокруг $BACKUP_FILE, где я установил $SPLIT_CMD. Но, если у меня есть пробел во имя моего каталога резервного копирования, он не работает. Кроме того, если я копирую и вставляю вывод из команды "echo" непосредственно в терминал, он отлично работает. Ясно, что я не понимаю, как bash ускользает от вещей.

4b9b3361

Ответ 1

Просто не помещайте целые команды в переменные. Вы столкнетесь с множеством проблем с попыткой восстановить приведенные аргументы.

также:

  • Избегайте использования имен переменных all-capitals в скриптах. Легкий способ стрелять в ногу.
  • Не используйте backquotes, вместо этого используйте $(...), он лучше всего подходит.

#! /bin/bash

if [ $# -ne 2 ]
then
    echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file"

Ответ 2

Я не уверен, но, возможно, стоит сначала запустить eval в командах.

Это позволит bash развернуть переменные $TAR_CMD и их полную ширину (так же, как команда echo делает на консоли, что вы говорите, работает)

Bash затем прочитает строку второй раз с расширенными переменными.

eval $TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

Я просто выполнил поиск Google, и эта страница выглядит так, как будто она может сделать достойную работу, объясняя, почему это необходимо. http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F

Ответ 3

eval не является приемлемой практикой, если имена ваших каталогов могут генерироваться ненадежными источниками. См BashFAQ # 48 больше о том, почему eval не должны использоваться, и BashFAQ # 50 больше на первопричину этой проблемы и ее собственных решений, некоторые из которых затрагивали ниже:

Если вам нужно наращивать свои команды с течением времени, используйте массивы:

tar_cmd=( tar cv "$directory" )
split_cmd=( split -b 1024m - "$backup_file" )
encrypt_cmd=( openssl des3 -salt )
"${tar_cmd[@]}" | "${encrypt_cmd[@]}" | "${split_cmd[@]}"

В качестве альтернативы, если это всего лишь определение ваших команд в одном центральном месте, используйте функции:

tar_cmd() { tar cv "$directory"; }
split_cmd() { split -b 1024m - "$backup_file"; }
encrypt_cmd() { openssl des3 -salt; }
tar_cmd | split_cmd | encrypt_cmd

Ответ 4

Есть смысл указывать команды и опции в переменных.

#! /bin/bash

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

. standard_tools    

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

${tar_create} "${directory}" | ${openssl} | ${split_1024} "$backup_file"

Вы можете переместить команды в другой файл, который вы используете, чтобы вы могли повторно использовать одни и те же команды и параметры во многих сценариях. Это очень удобно, когда у вас много скриптов, и вы хотите контролировать, как все они используют инструменты. Поэтому standard_tools будет содержать:

export tar_create="tar cv"
export openssl="openssl des3 -salt"
export split_1024="split -b 1024m -"

Ответ 5

Цитирование пробелов внутри переменных, так что оболочка будет правильно интерпретировать вещи, является трудной. Это тот тип вещи, который побуждает меня достичь более сильного языка. Будь то perl или python или ruby ​​или что-то еще (я выбираю perl, но это не всегда для всех), это просто то, что позволит вам обойти оболочку для цитирования.

Это не то, что я никогда не справлялся с либеральными дозами eval, но именно этот eval дает мне eebie-jeebies (становится совершенно новой головной болью, когда вы хотите взять пользовательский ввод и оценить его, хотя в в этом случае вы возьмете материал, который вы написали и угадали, вместо этого), и что у меня начались головные боли при отладке.

С perl, в качестве примера, я мог бы сделать что-то вроде:

@tar_cmd = ( qw(tar cv), $directory );
@encrypt_cmd = ( qw(openssl des3 -salt) );
@split_cmd = ( qw(split -b 1024m -), $backup_file );

Трудная часть здесь выполняет трубы - но немного IO:: Pipe, fork и повторное открытие stdout и stderr, и это не плохо. Некоторые скажут, что хуже, чем правильно цитировать оболочку, и я понимаю, откуда они идут, но для меня это легче читать, поддерживать и писать. Черт, кто-то может извлечь из этого тяжелую работу и создать модуль IO:: Pipeline и сделать все тривиальным: -)