如何延迟 shell 脚本中的命令替换?

Posted

技术标签:

【中文标题】如何延迟 shell 脚本中的命令替换?【英文标题】:How to delay a command substitution in a shell script? 【发布时间】:2018-06-04 09:54:32 【问题描述】:

我有一个 bash 脚本,简而言之,它通过其他命令生成文件(工作正常),然后在脚本中我对生成的文件执行 wc 命令。我的问题是我在 wc 命令上使用命令替换,当我执行脚本时,它会立即执行此替换,而不是等待在脚本中更早地生成文件。我有哪些选择?

该脚本是一个运行 Oracle SQLLoader 以将数据加载到 Oracle 表中的 shell 程序。 SQLLoader 命令生成一个包含被拒绝记录的文件,我正在尝试对其进行字数统计。这是我的代码:

#!/bin/sh

#   Set variables

program_name="My Program Name"
input_dir="/interface/inbound/hr_datafile"
log_dir="/interface/inbound/log"
bad_dir="/interface/inbound/hr_bad"
archive_dir="/interface/inbound/hr_archive"
input_base="Import_20171213"
input_ext="csv"

control_file="data_loader.ctl"

exit_status=0
d=`date "+%Y%m%d"`
t=`date "+%H%M"`


#   Check if data file exists, count records

if [ -f $input_dir/$input_base*.$input_ext ]; then
    data_file_name=`ls -1rt $input_dir/$input_base*.$input_ext | head -1 | cut -c 32-100`
    data_file_base=$data_file_name%.*
    echo "Data file name: " $data_file_name
    echo "Data file base: " $data_file_base
    no_of_recs=`expr $(wc -l $input_dir/$data_file_name | cut -c 1-8) - 1`
    echo "DEBUG no_of_recs: " $no_of_recs
    no_of_errs=0
else
    echo
    echo
    echo 
    echo "----------------------------- $program_name ------------------------------------------"
    echo "----------------------------------- Error report : ------------------------------------------------"
    echo 
    echo "Please place your Data files in the UNIX directory => "$input_dir
    echo "$program_name Process exiting ..."
    echo
    echo "---------------------------------------------------------------------------------------------------"
    echo
    echo
    echo
    echo
    echo 
    exit 1
fi


#   Run SQL*Loader

echo
echo "==> Loading Data...into MY_TABLE table"
echo

# Create a temporary control file to pass the data file name in
cat $XX_TOP/bin/$control_file | sed "s/:FILENAME/$data_file_name/g" > $XX_TOP/bin/$data_file_base_temp.ctl

# NOTE:  The following sqlldr format is used to "hide" the oracle user name and password
sqlldr errors=100000 skip=1 control=$XX_TOP/bin/$data_file_base_temp.ctl data=$input_dir/$data_file_name log=$log_dir/$data_file_base.log bad=$bad_dir/$data_file_base.bad <<-END_SQLLDR > /dev/null
apps/`cat $DB_SCRIPTS/.$ORACLE_SIDapps`
END_SQLLDR

exit_status=$?
echo "DEBUG exit_status " $exit_status    

# Remove temporary control file
rm -rf $XX_TOP/bin/$data_file_base_temp.ctl


#   Check for Errors

if [ -f $bad_dir/$data_file_base.bad ]; then
    echo
    echo "----------------------------- $program_name ------------------------------------------"
    echo "----------------------------------- Error report : ------------------------------------------------"
    echo
    grep 'Record' $log_dir/$data_file_base.log > $log_dir/$data_file_base.rec
    grep 'ORA' $log_dir/$data_file_base.log > $log_dir/$data_file_base.err
    paste $log_dir/$data_file_base.rec $log_dir/$data_file_base.err $bad_dir/$data_file_base.bad
    echo
    echo "<---------------------------------End of Error Report---------------------------------------------->"
    echo

    # Count error records
    no_of_errs=$(wc -l $bad_dir/$data_file_base.bad | cut -c 1-8)
    no_of_recs=$(expr $no_of_recs - $no_of_errs)

    # Remove temp files
    rm $log_dir/$data_file_base.rec
    rm $log_dir/$data_file_base.err
    rm $bad_dir/$data_file_base.bad
else
    echo "Bad File not found at $bad_dir/$data_file_base.bad"
fi

if (( $no_of_errs > 0 )); then 
    echo "Error found in data file..."
    exit_status=1
else
    # Archive the data file if no errors
    mv $input_dir/$data_file_name $archive_dir/$data_file_base_$d"_"$t.$input_ext
    echo "Data file archived to $archive_dir"
    exit_status=0
fi

echo
echo
echo 
echo "----------------------------- $program_name ------------------------------------------"
echo 
echo "Total records errored out         :" $no_of_errs
echo "Total records successfully read   :" $no_of_recs
echo "---------------------------------------------------------------------------------------------------"
echo




#   Final Exit Status



if [ $exit_status -eq 1 ]; then
    echo "==> Exiting process...Status : $exit_status"
    exit 1
fi

if 条件检查中引用的文件是生成的文件,所以我正在检查该文件是否存在,然后在其上运行 wc。我知道它过早地执行了,因为这个 wc 错误出现在我的脚本输出之前:

Data file name:  Import_20171213.csv
Data file base:  Import_20171213
DEBUG no_of_recs:  27

==> Loading Data...into MY_TABLE table

wc: 0653-755 Cannot open /interface/inbound/hr_bad/Import_20171213.bad.
Username:
SQL*Loader: Release 10.1.0.5.0 - Production on Thu Dec 21 12:42:39 2017

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Commit point reached - logical record count 27
Program exited with status 2

在代码中,.bad 文件的 wc 命令在 sqlldr 部分之后执行,但日志显示在调用 sqlldr 之前发生的错误。

任何想法将不胜感激!谢谢!

【问题讨论】:

你可以尝试在第一行之后使用 && Is there a TRY CATCH command in Bash的可能重复 旁注:您可以使用wc -l &lt; filename 来获取不带文件名的数字。你为什么使用eval?这甚至不是有效的语法。 您能否详细说明为什么您说“这会出现在我的脚本输出中之前”?您希望首先运行哪个命令?为什么? 您确定您的操作系统/shell 版本理解&lt;&lt;-LimitString 形式吗?看起来它只是没有看到heredoc的结束。如果您删除 -(因为它似乎是多余的?),或者将结束标记更改为 -END_SQLLDR(但不能同时),会发生什么? 【参考方案1】:

您没有看到您期望的所有输出 - 调用 SQL*Loader 之后的任何内容都丢失了 - 来自 wc 的错误出现在错误的位置,在 Username; 提示之前而不是之后。但是,给出您使用的构造不应该出错 - 如果文件不存在,if 测试应该停止到达该行。

问题是它没有看到heredoc 的结束。在heredoc 开始之后的所有命令都作为heredoc 处理的一部分执行,但不会去任何地方或显示到终端(如预期的那样),并且所有正在评估的方式导致@ 987654325@ 意外运行,即使该文件不存在。您会看到 stderr 的输出。

这一切都发生在 SQL*Loader 启动之前,因此您会在之后看到 Username: 提示。

从 cmets 看来,以heredoc 结尾的END_SQLLDR 确实缩进了您的实际代码,这并未反映在发布的问题中。当您使用 &lt;&lt;- heredoc 表单时,这意味着它使用空格而不是制表符缩进。这导致无法将其识别为此处文档的结尾。

来自The Linux Documentation Project:

结束限制字符串,在 here 文档的最后一行,必须从第一个字符位置开始。不能有前导空格。限制字符串后的尾随空格同样会导致意外行为。空格会阻止限制字符串被识别。

删除空格,以便这是行中的第一件事将修复它。

【讨论】:

以上是关于如何延迟 shell 脚本中的命令替换?的主要内容,如果未能解决你的问题,请参考以下文章

shell脚本sed命令如何使用变量替换掉包含指定字符串的整行

shell脚本命令如何 读取文本指定位置内容 写入另一文本指定位置并替换原内容

如何在shell脚本中修改添加替换指定文件中的内容

Linux Shell中的命令替换

shell脚本替换A、B两个文件里面的内容

如何避免shell脚本中的单个 * 被随机替换成目录下的一个文件名