Jak napisać skrypt, który akceptuje dane wejściowe z pliku lub ze standardowego wejścia?

57

Jak napisać skrypt akceptujący dane wejściowe z argumentu nazwy pliku lub ze standardowego wejścia?

na przykład możesz użyć w lessten sposób. można wykonać less filenamei równoważnie cat filename | less.

czy istnieje prosty sposób „po wyjęciu z pudełka”? czy muszę ponownie wynaleźć koło i napisać trochę logiki w skrypcie?

Gilad Hoch
źródło
@PlasmaPower Dopóki pytanie dotyczy SU, nie ma wymogu, aby zadawać pytania na innej stronie SE. Wiele witryn SE pokrywa się; generalnie nie musimy sugerować nakładającej się witryny, chyba że pytanie jest albo nie na temat (w takim przypadku głosuj na migrację), albo na temat, ale nie uzyskuje dużej odpowiedzi (w takim przypadku pytający powinien oflagować moderatora - uwaga / migracja, nie cross-post).
Bob

Odpowiedzi:

59

Jeśli argument pliku jest pierwszym argumentem skryptu, sprawdź, czy istnieje argument ( $1) i czy jest to plik. W przeciwnym razie przeczytaj dane wejściowe ze standardowego wejścia -

Więc twój skrypt może zawierać coś takiego:

#!/bin/bash
[ $# -ge 1 -a -f "$1" ] && input="$1" || input="-"
cat $input

np. wtedy możesz wywołać skrypt jak

./myscript.sh filename

lub

who | ./myscript.sh

Edytuj Niektóre wyjaśnienia skryptu:

[ $# -ge 1 -a -f "$1" ]- Jeśli co najmniej jeden argument wiersza poleceń ( $# -ge 1) ORAZ (operator -a) pierwszym argumentem jest plik (-f sprawdza, czy „$ 1” jest plikiem), wynik testu jest prawdziwy.

&&jest logicznym operatorem AND AND powłoki. Jeśli test jest prawdziwy, to przypisz input="$1"i cat $inputwyśle ​​plik.

||jest logicznym operatorem OR powłoki. Jeśli test jest fałszywy, następujące polecenia ||są analizowane. wejście jest przypisane do „-”. Polecenie cat -czyta z klawiatury.

Podsumowując, jeśli podano argument skryptu i jest to plik, wówczas do nazwy pliku przypisywane jest zmienne wejście. Jeśli nie ma poprawnego argumentu, cat czyta z klawiatury.

podejrzenie
źródło
co robi && input="$1" || input="-" i dlaczego znajduje się poza testoperatorem?
cmo
Dodałem edycję z wyjaśnieniem, które, mam nadzieję, pomoże.
podejrzany
Co jeśli skrypt ma wiele argumentów ( $@)?
g33kz0r
12

readodczytuje ze standardowego wejścia. Przekierowanie go z file ( ./script <someinput) lub przez pipe ( dosomething | ./script) nie sprawi, że będzie działać inaczej.

Wszystko, co musisz zrobić, to przejść przez wszystkie linie wejściowe (i nie różni się to od iteracji po liniach w pliku).

(przykładowy kod, przetwarza tylko jedną linię)

#!/bin/bash

read var
echo $var

Powtórzy echo pierwszego wiersza standardowego wejścia (przez <lub |).

Łukasz Daniluk
źródło
dzięki! wybrałem inną odpowiedź, ponieważ bardziej mi odpowiadała. pakowałem inny skrypt i nie chciałem zapętlać, dopóki wszystkie dane wejściowe nie zostaną odebrane (może być dużo danych wejściowych ... byłoby marnotrawstwem).
gilad hoch
4

Nie wspominasz o tym, jakiej powłoki zamierzasz użyć, więc przyjmuję bash, choć są to dość standardowe rzeczy w różnych powłokach.

Plik argumentów

Dostęp do argumentów można uzyskać za pomocą zmiennych $1- $n( $0zwraca polecenie użyte do uruchomienia programu). Powiedzmy, że mam skrypt, który catwyrzuca n liczby plików z separatorem między nimi:

#!/usr/bin/env bash
#
# Parameters:
#    1:   string delimiter between arguments 2-n
#    2-n: file(s) to cat out
for arg in ${@:2} # $@ is the array of arguments, ${@:2} slices it starting at 2.
do
   cat $arg
   echo $1
done

W tym przypadku przekazujemy nazwę pliku do cat. Jeśli jednak chcesz przekształcić dane w pliku (bez wyraźnego zapisywania i przepisywania), możesz również zapisać zawartość pliku w zmiennej:

file_contents=$(cat $filename)
[...do some stuff...]
echo $file_contents >> $new_filename

Czytaj ze standardowego

Jeśli chodzi o czytanie ze standardowego wejścia, większość powłok ma dość standardowe readwbudowanie, chociaż istnieją różnice w sposobie określania podpowiedzi (przynajmniej).

Strona podręcznika wbudowanego Bash ma dość zwięzłe wyjaśnienie read, ale ja wolę stronę hakerów Bash .

Po prostu:

read var_name

Wiele zmiennych

Aby ustawić wiele zmiennych, wystarczy podać wiele nazw parametrów, aby read:

read var1 var2 var3

read umieści następnie jedno słowo ze stdin w każdej zmiennej, zrzucając wszystkie pozostałe słowa do ostatniej zmiennej.

λ read var1 var2 var3
thing1 thing2 thing3 thing4 thing5
λ echo $var1; echo $var2; echo $var3
thing1
thing2
thing3 thing4 thing5

Jeśli wprowadzonych zostanie mniej słów niż zmiennych, pozostałe zmienne pozostaną puste (nawet jeśli zostały wcześniej ustawione):

λ read var1 var2 var3
thing1 thing2
λ echo $var1; echo $var2; echo $var3
thing1
thing2
# Empty line

Monity

-pCzęsto używam flagi do pytania:

read -p "Enter filename: " filename

Uwaga: ZSH i KSH (i być może inne) używają innej składni monitów:

read "filename?Enter filename: " # Everything following the '?' is the prompt

Wartości domyślne

To naprawdę nie jest readsztuczka, ale używam jej często w połączeniu z read. Na przykład:

read -p "Y/[N]: " reply
reply=${reply:-N}

Zasadniczo, jeśli zmienna (odpowiedź) istnieje, zwróć się, ale jeśli jest pusta, zwróć następujący parametr („N”).

Jacob Hayes
źródło
4

Najprostszym sposobem jest samodzielne przekierowanie standardowego wejścia:

if [ "$1" ] ; then exec < "$1" ; fi

Lub jeśli wolisz bardziej zwięzłą formę:

test "$1" && exec < "$1"

Teraz reszta skryptu może po prostu czytać ze standardowego wejścia. Oczywiście możesz zrobić podobnie z bardziej zaawansowanym parsowaniem opcji zamiast na stałe wpisywać pozycję nazwy pliku jako "$1".

R ..
źródło
execspróbuje wykonać argument jako polecenie, które nie jest tym, czego chcemy tutaj.
Suzana
@Suzana_K: Nie, gdy nie ma żadnych argumentów, jak tutaj. W takim przypadku po prostu zastępuje deskryptory plików samej powłoki, a nie procesu potomnego.
R ..
Skopiowałem if [ "$1" ] ; then exec < "$1" ; fiw skrypcie testowym i wyświetla komunikat o błędzie, ponieważ polecenie jest nieznane. To samo z postacią zwięzłą.
Suzana
1
@Suzana_K: Jakiej powłoki używasz? Jeśli to prawda, nie jest to działająca implementacja polecenia sh POSIX / powłoki Bourne'a.
R ..
GNU bash 4.3.11 na Linux Mint Qiana
Suzana
3

użyj (lub odetnij) czegoś, co już zachowuje się w ten sposób, i użyj "$@"

powiedzmy, że chcę napisać narzędzie, które zastąpi ciągi spacji w tekście tabulatorami

trjest najbardziej oczywistym sposobem na zrobienie tego, ale akceptuje tylko standardowe wejście, więc musimy odłączyć od cat:

$ cat entab1.sh
#!/bin/sh

cat "$@"|tr -s ' ' '\t'
$ cat entab1.sh|./entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ ./entab1.sh entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ 

na przykład, gdy używane narzędzie zachowuje się już w ten sposób, moglibyśmy sedzamiast tego zaimplementować :

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 
Aaron Davies
źródło
3

Możesz także:

#!/usr/bin/env bash

# Set variable input_file to either $1 or /dev/stdin, in case $1 is empty
# Note that this assumes that you are expecting the file name to operate on on $1
input_file="${1:-/dev/stdin}"

# You can now use "$input_file" as your file to operate on
cat "$input_file"

Więcej ciekawych sztuczek podstawiania parametrów w Bash, zobacz to .

Daniel
źródło
1
To jest fantastyczne! Używam uglifyjs < /dev/stdini działa wspaniale!
fregante
0

Możesz także zachować prostotę i użyć tego kodu


Kiedy tworzysz plik skryptu pass_it_on.sh z tym kodem,

#!/bin/bash

cat

Możesz biegać

cat SOMEFILE.txt | ./pass_it_on.sh

a cała zawartość standardowego ekranu zostanie po prostu wyrzucona na ekran.


Alternatywnie użyj tego kodu, aby zachować kopię standardowego pliku w pliku, a następnie wyrzucić ją na ekran.

#!/bin/bash

tmpFile=`mktemp`
cat > $tmpFile
cat $tmpFile    

a oto inny przykład, może bardziej czytelny, wyjaśniony tutaj:

http://mockingeye.com/blog/2013/01/22/reading-everything-stdin-in-a-bash-script/

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Baw się dobrze.

RaamEE

RaamEE
źródło
0

Najprostszym sposobem i zgodnym z POSIX jest:

file=${1--}

co jest równoważne z ${1:--}.

Następnie przeczytaj plik jak zwykle:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
kenorb
źródło