web-dev-qa-db-ja.com

入力をスクリプトにパイプする

CSVファイルをスプレッドシートXMLファイルに変換するシェルスクリプトをkshで作成しました。既存のCSVファイル(スクリプト内の変数へのパス)を取得し、新しい出力ファイル.xlsを作成します。スクリプトには定位置パラメーターはありません。現在、CSVのファイル名はスクリプトにハードコーディングされています。

パイプから入力CSVデータを取得できるようにスクリプトを修正し、.xls出力データもコマンドラインのファイルにパイプまたはリダイレクト(>)できるようにしたいと思います。

これはどのように達成されますか?

パイプから入力を取得するシェルスクリプトを記述する方法に関するドキュメントを見つけるのに苦労しています。 'read'はkbからのstd入力にのみ使用されるようです。

ありがとう。

編集:情報用の以下のスクリプト(質問への回答に従って、猫を介してパイプから入力を取得するように修正されました。

#!/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
20
Ben Hamilton

コマンドは、コマンドを開始するプロセスから標準入力を継承します。この場合、スクリプトは、実行する各コマンドに対して標準入力を提供します。簡単なスクリプト例:

#!/bin/bash
cat > foo.txt

catはスクリプトから標準入力を継承するため、シェルスクリプトにデータをパイプすると、catがそのデータを読み取ります。

$ echo "Hello world" | myscript.sh
$ cat foo.txt
Hello world

readコマンドは、スクリプトの標準入力を読み取りまたは処理する別のコマンドがない場合に、標準入力からシェル変数にテキストを読み取るためにシェルによって提供されます。

#!/bin/bash

read foo
echo "You entered '$foo'"

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

ここには1つの問題があります。 stdinに入力があることを確認するために最初にチェックせずにスクリプトを実行すると、何かが入力されるまでハングします。

そのため、これを回避するには、最初にstdinがあることを確認し、ない場合は、代わりにコマンドライン引数を使用します。

「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

出力:Data was piped to this script! Line: stuff Line: more stuff

では、入力がない場合はテストしてみましょう:

./testPipe.sh

出力:No input was found on stdin, skipping! No input given!

では、有効なファイル名を指定してテストしてみましょう:

./testPipe.sh test.txt

出力:No input was found on stdin, skipping! Filename specified: test.txt Doing things now..

そして最後に、無効なファイル名を使用してテストしましょう:

./testPipe.sh invalidFile.txt

出力:No input was found on stdin, skipping! No input given!

説明:readやcatなどのプログラムは、シェル内で使用可能な場合はstdinを使用し、そうでない場合は入力を待機します。

クレジットは、stdinの入力を確認する方法を示す彼の回答のこのページからマイクに行きます: https://unix.stackexchange.com/questions/33049/check-if-pipe-is-empty-and-run- a-command-on-the-data-if-it-isnt?newreg = fb5b291531dd4100837b12bc1836456f

27
Philip Reese

(スクリプトを作成する)外部プログラムがすでにstdinから入力を受け取っている場合、スクリプトは何もする必要がありません。たとえば、awkはstdinから読み取るため、1行あたりの単語数をカウントする短いスクリプトです。

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

それから

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

出力

1
2
3
5
glenn jackman