【问题标题】:Different pipeline behavior between sh and kshsh 和 ksh 之间的不同管道行为
【发布时间】:2014-01-08 10:50:31
【问题描述】:

我已将问题隔离到以下代码 sn-p:

  1. 请注意,当使用ksh 运行脚本时,空字符串将分配给LATEST_FILE_NAME='';但是当使用sh 运行时,脚本会正确地将值分配给变量$LATEST_FILE_NAME。这反过来会影响$FILE_LIST_COUNT 的值。
  2. 但由于脚本位于 KornShell (ksh) 中,我不确定是什么导致了问题。
  3. 当我在下面的行中注释掉tee 命令时,ksh 脚本工作正常并正确地将值分配给变量$LATEST_FILE_NAME
(cd $SOURCE_FILE_PATH; ls *.txt 2>/dev/null) | sort -r > ${SOURCE_FILE_PATH}/${FILE_LIST} | tee -a $LOG_FILE_PATH

请考虑:

1.源代码:script.sh

#!/usr/bin/ksh
set -vx # Enable debugging

SCRIPTLOGSDIR=/some/path/Scripts/TEST/shell_issue
SOURCE_FILE_PATH=/some/path/Scripts/TEST/shell_issue
# Log file
Timestamp=`date +%Y%m%d%H%M`
LOG_FILENAME="TEST_LOGS_${Timestamp}.log"
LOG_FILE_PATH="${SCRIPTLOGSDIR}/${LOG_FILENAME}"
## Temporary files
FILE_LIST=FILE_LIST.temp    #Will store all  extract filenames
FILE_LIST_COUNT=0           # Stores total number of  files

getFileListDetails(){
    rm -f $SOURCE_FILE_PATH/$FILE_LIST 2>&1 | tee -a $LOG_FILE_PATH

    # Get list of all files, Sort in reverse order, and store names of the  files line-wise. If no files are found, error is muted.
    (cd $SOURCE_FILE_PATH; ls *.txt 2>/dev/null) | sort -r > ${SOURCE_FILE_PATH}/${FILE_LIST} | tee -a $LOG_FILE_PATH

    if [[ ! -f $SOURCE_FILE_PATH/$FILE_LIST ]]; then
        echo "FATAL ERROR - Could not create a temp file for  file list.";exit 1;
    fi

    LATEST_FILE_NAME="$(cd $SOURCE_FILE_PATH; head -1 $FILE_LIST)";
    FILE_LIST_COUNT="$(cat $SOURCE_FILE_PATH/$FILE_LIST | wc -l)";

}

getFileListDetails;
exit 0;

2。使用shell时的输出 sh script.sh:

+ getFileListDetails
+ rm -f /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp
+ tee -a /some/path/Scripts/TEST/shell_issue/TEST_LOGS_201304300506.log
+ cd /some/path/Scripts/TEST/shell_issue
+ sort -r
+ tee -a /some/path/Scripts/TEST/shell_issue/TEST_LOGS_201304300506.log
+ ls 1.txt 2.txt 3.txt
+ [[ ! -f /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp ]]
cd $SOURCE_FILE_PATH; head -1 $FILE_LIST
++ cd /some/path/Scripts/TEST/shell_issue
++ head -1 FILE_LIST.temp
+ LATEST_FILE_NAME=3.txt
cat $SOURCE_FILE_PATH/$FILE_LIST | wc -l
++ cat /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp
++ wc -l
+ FILE_LIST_COUNT=3
exit 0;
+ exit 0

3。使用ksh时的输出 ksh script.sh:

+ getFileListDetails
+ tee -a /some/path/Scripts/TEST/shell_issue/TEST_LOGS_201304300507.log
+ rm -f /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp
+ 2>& 1
+ tee -a /some/path/Scripts/TEST/shell_issue/TEST_LOGS_201304300507.log
+ sort -r
+ 1> /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp
+ cd /some/path/Scripts/TEST/shell_issue
+ ls 1.txt 2.txt 3.txt
+ 2> /dev/null
+ [[ ! -f /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp ]]
+ cd /some/path/Scripts/TEST/shell_issue
+ head -1 FILE_LIST.temp
+ LATEST_FILE_NAME=''
+ wc -l
+ cat /some/path/Scripts/TEST/shell_issue/FILE_LIST.temp
+ FILE_LIST_COUNT=0
exit 0;+ exit 0

【问题讨论】:

  • 零输出几乎可以肯定意味着一个空文件或一个空变量,被评估为一个空文件。三重检查变量的拼写?更有用的是使用set -vx 打开 shell 调试。您将在执行之前看到每一行,然后在执行时看到将变量扩展为它们的值的行。查看那里以确保一切都按预期工作。祝你好运。
  • 谢谢@shellter!我会尝试使用set -vx 来调试它
  • 调试的怎么样?
  • 嗨 @AdrianFrühwirth,shellter - 我调试了代码,使用不同的 shell 解释器运行它,最后隔离了问题并在上面重现了它.. :) 谢谢
  • 在详细介绍您的 sn-p 的一些问题之前,您的更高目标是什么?为什么您认为需要反向文件列表?你想用它做什么?

标签: shell scripting sh ksh pipeline


【解决方案1】:

好的,接下来……这是一个棘手而微妙的问题。答案在于管道是如何实现的。 POSIX 声明

如果管道不在后台(参见异步列表),shell 将等待管道中指定的最后一个命令完成,也可能等待所有命令完成。)

注意关键字可能。许多 shell 以 all 命令需要完成的方式实现这一点,例如请参阅 联机帮助页:

shell 在返回值之前等待管道中的所有命令终止。

注意 手册页中的措辞:

每个命令(可能是最后一个命令除外)都作为单独的进程运行; shell 等待最后一个命令终止。

在您的示例中,最后一个命令是 tee 命令。由于在之前的命令中将stdout重定向到${SOURCE_FILE_PATH}/${FILE_LIST},所以tee没有输入,它立即退出。简单地说,tee 比之前的重定向更快,这意味着在您读取文件时,您的文件可能还没有完成写入。您可以通过在整个命令末尾添加sleep 来测试它(这不是修复!):

$ ksh -c 'ls /tmp/* | sort -r > /tmp/foo.txt | tee /tmp/bar.txt; echo "[$(head -n 1 /tmp/foo.txt)]"'
[]

$ ksh -c 'ls /tmp/* | sort -r > /tmp/foo.txt | tee /tmp/bar.txt; sleep 0.1; echo "[$(head -n 1 /tmp/foo.txt)]"'
[/tmp/sess_vo93c7h7jp2a49tvmo7lbn6r63]

$ bash -c 'ls /tmp/* | sort -r > /tmp/foo.txt | tee /tmp/bar.txt; echo "[$(head -n 1 /tmp/foo.txt)]"'
[/tmp/sess_vo93c7h7jp2a49tvmo7lbn6r63]

话虽如此,这里还有其他一些需要考虑的事情:

  1. 始终引用您的变量,尤其是在处理文件时,以避免出现通配、分词(如果您的路径包含空格)等问题:

    do_something "${this_is_my_file}"

  2. head -1 已弃用,请使用head -n 1

  3. 如果一行只有一个命令,结尾的分号 ; 是多余的...跳过它

  4. LATEST_FILE_NAME="$(cd $SOURCE_FILE_PATH; head -1 $FILE_LIST)"

    不需要先cd进入目录,只需将整个路径指定为head的参数即可:

    LATEST_FILE_NAME="$(head -n 1 "${SOURCE_FILE_PATH}/${FILE_LIST}")"

  5. FILE_LIST_COUNT="$(cat $SOURCE_FILE_PATH/$FILE_LIST | wc -l)"

    这被称为Useless Use Of Cat,因为不需要cat - wc 可以处理文件。您可能使用它是因为wc -l myfile 的输出包含文件名,但您可以使用例如FILE_LIST_COUNT="$(wc -l < "${SOURCE_FILE_PATH}/${FILE_LIST}")" 代替。

此外,您还需要阅读Why you shouldn't parse the output of ls(1)How can I get the newest (or oldest) file from a directory?

【讨论】:

  • 很棒的分析@Adrian,也感谢您的建议和链接!现在正在研究它们..
猜你喜欢
  • 1970-01-01
  • 2015-02-11
  • 2021-09-18
  • 1970-01-01
  • 2013-08-12
  • 1970-01-01
  • 2014-11-01
  • 1970-01-01
  • 2018-05-07
相关资源
最近更新 更多