sh和ksh之间的管道行为不同

时间:2013-04-17 20:10:53

标签: shell scripting sh ksh pipeline

我已将问题隔离到以下代码段:

  1. 请注意,当使用LATEST_FILE_NAME=''运行脚本时,会将空字符串分配给ksh;但是当使用$LATEST_FILE_NAME运行时,脚本会正确地将值分配给变量sh。这又会影响$FILE_LIST_COUNT
  2. 的价值
  3. 但由于脚本位于KornShell(ksh)中,我不确定是什么原因引起了这个问题。
  4. 当我在下面的行中注释掉tee命令时,ksh脚本正常工作并正确地将值赋给变量$LATEST_FILE_NAME
  5. (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
    

1 个答案:

答案 0 :(得分:2)

好的,这里......这是一个棘手而微妙的问题。答案在于如何实施管道。 POSIX表示

  

如果管道不在后台(请参阅异步列表),shell应等待管道中指定的最后一个命令完成,并且还可以等待所有命令完成。)

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

  

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

请注意联机帮助页中的措辞:

  

除了最后一个命令之外,每个命令都作为一个单独的进程运行; shell等待最后一个命令终止。

在您的示例中,最后一个命令是tee命令。由于tee没有输入,因为您之前在命令中将stdout重定向到${SOURCE_FILE_PATH}/${FILE_LIST},它会立即退出。换句话说,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}")"

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