Переменные как команды в скриптах bash - PullRequest
21 голосов
/ 27 апреля 2009

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

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

#! /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» прямо в терминал, он будет работать нормально. Ясно, что есть кое-что, чего я не понимаю, о том, как Бэш избегает вещей.

Ответы [ 5 ]

43 голосов
/ 27 апреля 2009

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

Также:

  1. Избегайте использования имен переменных с прописными буквами в скриптах. Простой способ выстрелить себе в ногу.
  2. Не используйте обратные кавычки, вместо этого используйте $ (...), это будет лучше.

#! /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"
6 голосов
/ 01 июня 2016

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
6 голосов
/ 27 апреля 2009

Я не уверен, но, возможно, стоит сначала запустить 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

4 голосов
/ 28 апреля 2009

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

#! /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 -"
1 голос
/ 27 апреля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...