将输入导入脚本

60

我用ksh编写了一个shell脚本,将CSV文件转换为电子表格XML文件。它接受一个现有的CSV文件(路径在脚本中作为变量),然后创建一个新的输出文件xls。该脚本没有位置参数。CSV文件的文件名目前是硬编码到脚本中的。

我想修改脚本,使其可以从管道中读取输入CSV数据,并且xls输出数据也可以通过命令行进行管道传输或重定向(>)到文件中。

如何实现这一点?

我正在努力寻找关于如何编写从管道中读取输入的Shell脚本的文档。似乎"read"只用于从键盘读取标准输入。

谢谢。

编辑:以下为脚本信息(现已根据问题答案的建议修改为通过cat从管道中获取输入)。

#!/bin/ksh
#Script to convert a .csv data to "Spreadsheet ML" XML format - the XML scheme for Excel 2003
#
#   Take CSV data as standard input
#   Out XLS data as standard output
#

DATE=`date +%Y%m%d`

#define tmp files
INPUT=tmp.csv
IN_FILE=in_file.csv

#take standard input and save as $INPUT (tmp.csv)
cat > $INPUT

#clean input data and save as $IN_FILE (in_file.csv)
grep '.' $INPUT | sed 's/ *,/,/g' | sed 's/, */,/g' > $IN_FILE

#delete original $INPUT file (tmp.csv)
rm $INPUT

#detect the number of columns and rows in the input file
ROWS=`wc -l < $IN_FILE | sed 's/ //g' `
COLS=`awk -F',' '{print NF; exit}' $IN_FILE`
#echo "Total columns is $COLS"
#echo "Total rows  is $ROWS"

#create start of Excel File
echo "<?xml version=\"1.0\"?>
<?mso-application progid=\"Excel.Sheet\"?> 
<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"
        xmlns:o=\"urn:schemas-microsoft-com:office:office\"
        xmlns:x=\"urn:schemas-microsoft-com:office:excel\"
        xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"
        xmlns:html=\"http://www.w3.org/TR/REC-html40\">
<DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">
      <Author>Ben Hamilton</Author>
      <LastAuthor>Ben Hamilton</LastAuthor>
      <Created>${DATE}</Created>
      <Company>MCC</Company>
      <Version>10.2625</Version>
</DocumentProperties>
<ExcelWorkbook xmlns=\"urn:schemas-microsoft-com:office:excel\">
        <WindowHeight>6135</WindowHeight>
        <WindowWidth>8445</WindowWidth>
        <WindowTopX>240</WindowTopX>
        <WindowTopY>120</WindowTopY>
        <ProtectStructure>False</ProtectStructure>
        <ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>

<Styles>
      <Style ss:ID=\"Default\" ss:Name=\"Normal\">
            <Alignment ss:Vertical=\"Bottom\" />
            <Borders />
            <Font />
            <Interior />
            <NumberFormat />
            <Protection />
      </Style>
      <Style ss:ID=\"AcadDate\">
      <NumberFormat ss:Format=\"Short Date\"/>    
      </Style> 
</Styles>
<Worksheet ss:Name=\"Sheet 1\">
<Table>
<Column ss:AutoFitWidth=\"1\" />"

#for each row in turn, create the XML elements for row/column
r=1
while (( r <= $ROWS ))
do
   echo "<Row>\n" 
    c=1
    while (( c <= $COLS ))
    do
        DATA=`sed -n "${r}p" $IN_FILE | cut -d "," -f $c `

        if [[ "${DATA}" == [0-9][0-9]\.[0-9][0-9]\.[0-9][0-9][0-9][0-9] ]]; then

            DD=`echo $DATA | cut -d "." -f 1`
            MM=`echo $DATA | cut -d "." -f 2`
            YYYY=`echo $DATA | cut -d "." -f 3`     
            echo "<Cell ss:StyleID=\"AcadDate\"><Data ss:Type=\"DateTime\">${YYYY}-${MM}-${DD}T00:00:00.000</Data></Cell>"
        else        
            echo "<Cell><Data ss:Type=\"String\">${DATA}</Data></Cell>" 
        fi
        (( c+=1 ))
    done
    echo "</Row>"
   (( r+=1 ))
done

echo "</Table>\n</Worksheet>\n</Workbook>"


rm $IN_FILE > /dev/null

exit 0

展示给我们脚本... - devnull
3个回答

81

命令继承其启动它们的进程的标准输入。在您的情况下,您的脚本为每个运行的命令提供其标准输入。以下是一个简单的示例脚本:

#!/bin/bash
cat > foo.txt
将数据传输到你的shell脚本中,会导致cat读取该数据,因为cat继承了来自脚本的标准输入。
$ echo "Hello world" | myscript.sh
$ cat foo.txt
Hello world

read 命令由 shell 提供,用于从标准输入中读取文本并将其存储到 shell 变量中,如果您没有其他命令来读取或处理脚本的标准输入。

#!/bin/bash

read foo
echo "You entered '$foo'"

$ echo bob | myscript.sh
You entered 'bob'

1
似乎不支持多行输入。 - Matthias
4
不,read 函数只会读取一行(也就是到第一个换行符为止)。 - chepner

71

这里有一个问题。如果您在运行脚本之前没有检查标准输入是否有输入,那么它将一直挂起直到有输入为止。

因此,为了解决这个问题,您可以先检查标准输入是否存在,如果不存在,则使用命令行参数(如果已提供)。

创建一个名为“testPipe.sh”的脚本。

#!/bin/bash
# Check to see if a pipe exists on stdin.
if [ -p /dev/stdin ]; then
        echo "Data was piped to this script!"
        # If we want to read the input line by line
        while IFS= read line; do
                echo "Line: ${line}"
        done
        # Or if we want to simply grab all the data, we can simply use cat instead
        # cat
else
        echo "No input was found on stdin, skipping!"
        # Checking to ensure a filename was specified and that it exists
        if [ -f "$1" ]; then
                echo "Filename specified: ${1}"
                echo "Doing things now.."
        else
                echo "No input given!"
        fi
fi

然后进行测试:

让我们向test.txt文件中添加一些内容,然后将输出重定向到我们的脚本。

printf "stuff\nmore stuff\n" > test.txt
cat test.txt | ./testPipe.sh

输出: 数据已通过管道传输到此脚本! 行:stuff 行:more stuff

现在让我们测试一下如果没有提供任何输入:

./testPipe.sh

输出: stdin中未找到输入,跳过! 没有提供输入!

现在让我们测试是否提供有效的文件名:

./testPipe.sh test.txt

输出: 未在标准输入中发现输入,跳过! 指定的文件名为:test.txt 现在正在进行操作..

最后,让我们测试一个无效的文件名:

./testPipe.sh invalidFile.txt

输出:未在stdin中找到输入,跳过!未提供输入!

解释:类似于read和cat这样的程序将在shell内部使用stdin(标准输入),否则它们将等待输入。

感谢Mike在此页面上的回答中展示了如何检查stdin输入。


这是一个更好的答案,特别是在与CSV文件相关的问题上下文中,人们可以假设管道输入包含多行。 - Aurovrata

10

如果您编写的外部程序已经从stdin接收输入,则您的脚本不需要执行任何操作。例如,awk从stdin读取,因此一个简短的脚本可以计算每行的单词数:

#!/bin/sh
awk '{print NF}'

那么

./myscript.sh <<END
one
one two
one two three
END

输出

1
2
3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接