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

Shell sh和ksh之间的不同管道行为,shell,scripting,sh,ksh,pipeline,bash,Shell,Scripting,Sh,Ksh,Pipeline,Bash,我已将问题隔离到以下代码段: 请注意,当使用ksh运行脚本时,会将空字符串分配给LATEST_FILE_NAME=';但是当使用sh运行时,脚本会正确地将值分配给变量$LATEST\u FILE\u NAME。这反过来会影响$FILE\u LIST\u COUNT的值 但由于脚本在KornShell(ksh)中,我不确定是什么导致了这个问题 当我注释掉下一行中的tee命令时,ksh脚本工作正常,并正确地将值分配给变量$LATEST\u FILE\u NAME 请考虑: 1。源代码:script

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

  • 请注意,当使用
    ksh
    运行脚本时,会将空字符串分配给
    LATEST_FILE_NAME='
    ;但是当使用
    sh
    运行时,脚本会正确地将值分配给变量
    $LATEST\u FILE\u NAME
    。这反过来会影响
    $FILE\u LIST\u COUNT的值
  • 但由于脚本在KornShell(ksh)中,我不确定是什么导致了这个问题
  • 当我注释掉下一行中的
    tee
    命令时,ksh脚本工作正常,并正确地将值分配给变量
    $LATEST\u FILE\u NAME

  • 请考虑:

    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
    

    好了,这是一个微妙的问题。答案在于管道是如何实现的。声明

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

    注意关键字may。许多shell以所有命令都需要完成的方式实现此功能,例如,请参见手册页:

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

    请注意手册页中的措辞:

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

    在您的示例中,最后一个命令是
    tee
    命令。因为在前面的命令中将
    stdout
    重定向到
    ${SOURCE\u FILE\u PATH}/${FILE\u 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]
    
    尽管如此,这里还有一些其他需要考虑的事情:

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

    dou\u something“${这是我的文件}”

  • head-1
    已弃用,请使用
    head-n1

  • 如果一行上只有一个命令,则结束分号
    是多余的…跳过它就行了

  • LATEST\u FILE\u NAME=“$(cd$SOURCE\u FILE\u PATH;head-1$FILE\u LIST)”

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

    LATEST_FILE_NAME=“$(head-n1”${SOURCE_FILE_PATH}/${FILE_LIST}”)”

  • FILE_LIST_COUNT=“$(cat$SOURCE_FILE_PATH/$FILE_LIST|wc-l)”

    之所以调用它,是因为不需要
    cat
    -
    wc
    可以处理文件。您使用它可能是因为
    wc-l myfile
    的输出包含文件名,但您可以使用例如
    FILE\u LIST\u COUNT=“$(wc-l<“${SOURCE\u FILE\u PATH}/${FILE\u LIST}”)”
    代替


  • 此外,您还需要读取和。

    零输出几乎肯定意味着一个空文件或一个空变量,被计算为一个空文件。三次检查变量的拼写?更有用的是使用
    set-vx
    打开shell调试。您将看到执行前的每一行,然后是执行时的每一行,变量扩展到它们的值。看看那里,确保一切都按照你的预期进行。祝你好运。谢谢@Sheller!我将尝试使用
    set-vx
    进行调试。调试进行得如何?嗨@AdrianFrühwirth,Shelleter-我调试了代码,使用不同的shell解释器运行了代码,最后隔离了问题,并在上面复制了它:)感谢在详细介绍您的代码片段中的一些错误之前,您的更高目标是什么?为什么您认为需要一个相反的文件列表?你想用它做什么?很棒的分析@Adrian,也谢谢你的建议和链接!现在正在研究它们。。
    $ 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]