变量作为bash脚本中的命令

时间:2009-04-27 18:36:22

标签: bash unix shell

我正在编写一个非常简单的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"

运行此命令失败,显示:

  

拆分:“foo / 2009-04-27T14-32-04.backup”aa:没有这样的文件或目录

我可以通过移除$BACKUP_FILE周围的引号来解决此问题,我设置$SPLIT_CMD。但是,如果我的备份目录名称中有空格,则它不起作用。此外,如果我将“echo”命令的输出直接复制并粘贴到终端,它可以正常工作。显然,我不明白Bash是如何逃避事情的。

5 个答案:

答案 0 :(得分:43)

根本不要将整个命令放在变量中。尝试恢复引用的参数会遇到很多麻烦。

此外:

  1. 避免在脚本中使用全大写变量名称。轻松拍摄自己的脚步。
  2. 不要使用反引号,而是使用$(...),它会更好地嵌套。

  3. #! /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"
    

答案 1 :(得分:6)

我不确定,但可能值得首先在命令上运行eval。

这会让bash将变量$ TAR_CMD扩展到它们的全部宽度(正如echo命令对控制台所做的那样,你认为有效)

然后,Bash会在扩展变量的情况下第二次读取该行。

eval $TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

我刚刚进行了谷歌搜索,这个页面似乎可以在解释为什么需要这样做时做得不错。 http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F

答案 2 :(得分:6)

如果您的目录名称可以由不受信任的来源生成,则

eval不是可接受的做法。有关不应使用eval的原因的更多信息,请参阅BashFAQ #48;有关此问题的根本原因及其正确的解决方案,请参阅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

答案 3 :(得分: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 -"

答案 4 :(得分:1)

引用变量中的空格以使shell正确地重新解释事物 hard 。正是这种事情促使我找到更强的语言。无论是perl,python还是ruby还是其他(我选择perl,但并不总是适合每个人),只需某些就可以绕过shell进行引用。

并不是说我从未设法用自由剂量的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,分叉,并重新打开stdout和stderr,这也不错。有些人会说这比正确引用shell更糟糕,我知道它们来自哪里,但对我来说,这更容易阅读,维护和编写。哎呀,有人可以从中解决这个问题并创建一个IO :: Pipeline模块并使整个事情变得微不足道; - )