web-dev-qa-db-fra.com

Tuyau d'entrée dans un script

J'ai écrit un script Shell dans ksh pour convertir un fichier CSV en fichier XML Spreadsheet. Il prend un fichier CSV existant (dont le chemin est une variable dans le script), puis crée un nouveau fichier de sortie .xls. Le script n'a pas de paramètres positionnels. Le nom de fichier du CSV est actuellement codé en dur dans le script.

Je voudrais modifier le script afin qu'il puisse prendre les données CSV d'entrée d'un tube, et que les données de sortie .xls puissent également être redirigées ou redirigées (>) vers un fichier sur la ligne de commande.

Comment y parvient-on?

J'ai du mal à trouver de la documentation sur la façon d'écrire un script Shell pour prendre les entrées d'un tube. Il semble que 'read' ne soit utilisé que pour l'entrée std de kb.

Merci.

Edit: script ci-dessous pour info (maintenant modifié pour prendre l'entrée d'un tuyau via le chat, selon la réponse à la question.

#!/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

Les commandes héritent de leur entrée standard du processus qui les démarre. Dans votre cas, votre script fournit son entrée standard pour chaque commande qu'il exécute. Un exemple de script simple:

#!/bin/bash
cat > foo.txt

Le fait de canaliser des données dans votre script Shell fait que cat lit ces données, puisque cat hérite de son entrée standard de votre script.

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

La commande read est fournie par le Shell pour lire le texte de l'entrée standard dans une variable Shell si vous n'avez pas d'autre commande pour lire ou traiter l'entrée standard de votre script.

#!/bin/bash

read foo
echo "You entered '$foo'"

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

Il y a un problème ici. Si vous exécutez le script sans vérifier d'abord qu'il y a une entrée sur stdin, il se bloquera jusqu'à ce que quelque chose soit tapé.

Donc, pour contourner ce problème, vous pouvez vérifier qu'il y a d'abord stdin, et sinon, utiliser un argument de ligne de commande à la place s'il est donné.

Créez un script appelé "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

Puis pour tester:

Ajoutons des éléments à un fichier test.txt, puis dirigons la sortie vers notre script.

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

Production: Data was piped to this script! Line: stuff Line: more stuff

Maintenant, testons si nous ne fournissons aucune entrée:

./testPipe.sh

Production: No input was found on stdin, skipping! No input given!

Maintenant, testons si nous fournissons un nom de fichier valide:

./testPipe.sh test.txt

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

Et enfin, testons en utilisant un nom de fichier invalide:

./testPipe.sh invalidFile.txt

Production: No input was found on stdin, skipping! No input given!

Explication: Des programmes comme read et cat utiliseront le stdin s'il est disponible dans le shell, sinon ils attendront l'entrée.

Nous remercions Mike de cette page dans sa réponse montrant comment vérifier l'entrée 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

Si le programme externe (que vous scriptez) prend déjà une entrée de stdin, votre script n'a rien à faire. Par exemple, awk lit depuis stdin, donc un petit script pour compter les mots par ligne:

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

Alors

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

les sorties

1
2
3
5
glenn jackman