[Bash] Stdin escapen?

Pagina: 1
Acties:

  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Hallo,

Ik heb een scriptje geschreven dat gebruik maakt van de stdin.
Een dummy versie van dit scriptje:

code:
1
2
3
4
#!/bin/bash

# Neem de input en mail deze naar ergens
cat - | mail -s 'Test' iemand@ergens.com


Dit werkt nu, maar ik heb echter héél lang gesukkeld tot ik ervoor kon zorgen dat de inhoud van stdin niet geinterpreteerd werd door bash.

Ik deed eerst dit:
code:
1
2
3
4
5
6
#!/bin/bash

MESSAGE=`cat -`

# Neem de input en mail deze naar ergens
echo $MESSAGE | mail -s 'Test' iemand@ergens.com


Dit werkte wel voor inhoud zonder speciale tekens, maar van zodra er een speciaal teken in de stdin content staat, dan wordt deze expanded door bash.
Bv: Een * teken wordt vertaald naar de inhoud van de werkdir ...

Ik vroeg me af hoe je dan de inhoud van stdin in een variabele krijgt, ZONDER bash expansion?

  • jurp5
  • Registratie: Februari 2003
  • Laatst online: 25-01 12:45
code:
1
echo "$MESSAGE" | mail -s 'Test' iemand@ergens.com

zou moeten werken

Verwijderd

DieterVDW schreef op donderdag 28 augustus 2008 @ 11:26:
Hallo,

Ik heb een scriptje geschreven dat gebruik maakt van de stdin.
Een dummy versie van dit scriptje:

code:
1
2
3
4
#!/bin/bash

# Neem de input en mail deze naar ergens
cat - | mail -s 'Test' iemand@ergens.com


Dit werkt nu, maar ik heb echter héél lang gesukkeld tot ik ervoor kon zorgen dat de inhoud van stdin niet geinterpreteerd werd door bash.

Ik deed eerst dit:
code:
1
2
3
4
5
6
#!/bin/bash

MESSAGE=`cat -`

# Neem de input en mail deze naar ergens
echo $MESSAGE | mail -s 'Test' iemand@ergens.com


Dit werkte wel voor inhoud zonder speciale tekens, maar van zodra er een speciaal teken in de stdin content staat, dan wordt deze expanded door bash.
Bv: Een * teken wordt vertaald naar de inhoud van de werkdir ...

Ik vroeg me af hoe je dan de inhoud van stdin in een variabele krijgt, ZONDER bash expansion?
dit doe je zo:

code:
1
2
3
4
#!/usr/bin/env bash

read var
echo "${var}"

  • jurp5
  • Registratie: Februari 2003
  • Laatst online: 25-01 12:45
Verwijderd schreef op donderdag 28 augustus 2008 @ 12:02:
[...]


dit doe je zo:

code:
1
2
3
4
#!/usr/bin/env bash

read var
echo "${var}"
Ik denk niet dat dat gaat werken, read pakt maar een regel en cat - gaat door tot een EOF

Verwijderd

jurp5 schreef op donderdag 28 augustus 2008 @ 12:17:
[...]

Ik denk niet dat dat gaat werken, read pakt maar een regel en cat - gaat door tot een EOF
read [-ers] [-u fd] [-t timeout] [-a aname] [-p
prompt] [-n nchars] [-d delim] [name ...]
One line is read from the standard input, or from

GNU Bash-3.2 2006 September 28 89

BASH(1) BASH(1)

the file descriptor fd supplied as an argument to
the -u option, and the first word is assigned to
the first name, the second word to the second name,
and so on, with leftover words and their interven-
ing separators assigned to the last name. If there
are fewer words read from the input stream than
names, the remaining names are assigned empty val-
ues. The characters in IFS are used to split the
line into words. The backslash character (\) may
be used to remove any special meaning for the next
character read and for line continuation. Options,
if supplied, have the following meanings:
-a aname
The words are assigned to sequential indices
of the array variable aname, starting at 0.
aname is unset before any new values are
assigned. Other name arguments are ignored.
-d delim
The first character of delim is used to ter-
minate the input line, rather than newline.
-e If the standard input is coming from a ter-
minal, readline (see READLINE above) is used
to obtain the line.
-n nchars
read returns after reading nchars characters
rather than waiting for a complete line of
input.
-p prompt
Display prompt on standard error, without a
trailing newline, before attempting to read
any input. The prompt is displayed only if
input is coming from a terminal.
-r Backslash does not act as an escape charac-
ter. The backslash is considered to be part
of the line. In particular, a backslash-
newline pair may not be used as a line con-
tinuation.
-s Silent mode. If input is coming from a ter-
minal, characters are not echoed.
-t timeout
Cause read to time out and return failure if
a complete line of input is not read within
timeout seconds. This option has no effect
if read is not reading input from the termi-
nal or a pipe.
-u fd Read input from file descriptor fd.

If no names are supplied, the line read is assigned
to the variable REPLY. The return code is zero,
unless end-of-file is encountered, read times out,
or an invalid file descriptor is supplied as the
argument to -u.
hier een voorbeeldje...
code:
1
2
3
4
5
#!/usr/bin/env bash

read -d "\cd" var 

echo "${var}"


edit:

voorbeeld

  • sam.vimes
  • Registratie: Januari 2007
  • Laatst online: 07-01 22:10
DieterVDW schreef op donderdag 28 augustus 2008 @ 11:26:
[...]
code:
1
2
3
4
#!/bin/bash

# Neem de input en mail deze naar ergens
cat - | mail -s 'Test' iemand@ergens.com
Dit is de zoveelste inzending voor de Useless use of cat-Award (http://partmaps.org/era/unix/award.html).
Bash:
1
2
3
#!/bin/bash
# Neem de input en mail deze naar ergens
mail -s 'Test' iemand@ergens.com

doet precies hetzelfde.
[...]
Ik vroeg me af hoe je dan de inhoud van stdin in een variabele krijgt, ZONDER bash expansion?
De oorspronkelijke oplossing is correct:
Bash:
1
MESSAGE=`cat -`

(of in "native" bash:
Bash:
1
MESSAGE=$(cat)

De "-" achter cat is niet nodig omdat cat by default uit stdin leest en ik vind de constructie met $() beter leesbaar dan die met backquotes ``.)

Waar het fout gaat, is de expansie van MESSAGE, dus het moment dat er een $ voor komt. Bij het interpreteren van een commando in bash vinden er verschillende stappen van expansie en interpolatie achter elkaar plaats. Waar de TS last van heeft is
  • interpolatie van $, gevolgd door
  • expansie van wildcards
In dit geval is het eerste wel gewenst, het tweede niet. Precies dat effect kan worden bereikt door (dubbele) quotes om de $-expansie te plaatsen:
Bash:
1
echo "$MESSAGE"

interpoleert de variabele MESSAGE, en door de quotes wordt verdere interpretatie voorkomen (de expansie van wildcards).

Overigens is het IMHO een gevaarlijk idee om de inhoud van een file compleet in een variabele te stoppen en er dan mee te gaan slepen. Als de inhoud van de file meer is dan de maximale grootte van een command-line (ARG_MAX in limits.h, 4096 in POSIX, 32768 of zo in Linux) heb je al een probleem.

Verwijderd

sam.vimes schreef op donderdag 28 augustus 2008 @ 14:49:
[...]

Dit is de zoveelste inzending voor de Useless use of cat-Award (http://partmaps.org/era/unix/award.html).
Bash:
1
2
3
#!/bin/bash
# Neem de input en mail deze naar ergens
mail -s 'Test' iemand@ergens.com

doet precies hetzelfde.

[...]

De oorspronkelijke oplossing is correct:
Bash:
1
MESSAGE=`cat -`

(of in "native" bash:
Bash:
1
MESSAGE=$(cat)

De "-" achter cat is niet nodig omdat cat by default uit stdin leest en ik vind de constructie met $() beter leesbaar dan die met backquotes ``.)

Waar het fout gaat, is de expansie van MESSAGE, dus het moment dat er een $ voor komt. Bij het interpreteren van een commando in bash vinden er verschillende stappen van expansie en interpolatie achter elkaar plaats. Waar de TS last van heeft is
  • interpolatie van $, gevolgd door
  • expansie van wildcards
In dit geval is het eerste wel gewenst, het tweede niet. Precies dat effect kan worden bereikt door (dubbele) quotes om de $-expansie te plaatsen:
Bash:
1
echo "$MESSAGE"

interpoleert de variabele MESSAGE, en door de quotes wordt verdere interpretatie voorkomen (de expansie van wildcards).

Overigens is het IMHO een gevaarlijk idee om de inhoud van een file compleet in een variabele te stoppen en er dan mee te gaan slepen. Als de inhoud van de file meer is dan de maximale grootte van een command-line (ARG_MAX in limits.h, 4096 in POSIX, 32768 of zo in Linux) heb je al een probleem.
Is de maximale variabele grootte in bash echt ARG_MAX? weet je toevallig ook waarom dat zo is want nu ben ik benieuwd.

read is trouwens wel iets sneller omdat het een builtin is van bash.

  • sam.vimes
  • Registratie: Januari 2007
  • Laatst online: 07-01 22:10
Verwijderd schreef op donderdag 28 augustus 2008 @ 15:54:
[...]
Is de maximale variabele grootte in bash echt ARG_MAX? weet je toevallig ook waarom dat zo is want nu ben ik benieuwd.

read is trouwens wel iets sneller omdat het een builtin is van bash.
Ik heb het uitgeprobeerd. Eerst eens kijken hoeveel ARG_MAX is (in cygwin):
Bash:
1
2
$ find /usr/include/ -type f -exec grep -w ARG_MAX {} +
/usr/include/sys/syslimits.h:#define ARG_MAX 65536 /* max bytes for an exec function */

Nu een commando met flink wat output op mijn systeem (alle files in /tmp zonder spaties in hun naam):
Bash:
1
2
3
4
$ MSG=$(find /tmp ! -path '* *')
$ echo "$MSG" | wc -c
199211
$

ARG_MAX is hier dus niet de limiet. Misschien omdat assigment en echo builtins zijn. Laten we het eens proberen met een extern commando (ls). Het gaat me alleen om een eventuele foutmelding, vandaar de >/dev/null.
Bash:
1
2
$ ls -d $MSG > /dev/null
$

Geen melding. Zelfde verhaal (met vergelijkbare getallen) op mijn Debian machine. 't Ziet ernaar uit dat ARG_MAX geen effect heeft.

[ Voor 0% gewijzigd door sam.vimes op 29-08-2008 11:35 . Reden: [code]tag vergeten ]


Verwijderd

sam.vimes schreef op vrijdag 29 augustus 2008 @ 11:34:
[...]


Ik heb het uitgeprobeerd. Eerst eens kijken hoeveel ARG_MAX is (in cygwin):
[...]
Bash:
1
2
3
4
$ MSG=$(find /tmp ! -path '* *')
$ echo "$MSG" | wc -c
199211
$

ARG_MAX is hier dus niet de limiet.
Euh ... de wc -c leest het aantal karakters op je stdin, i.e. het aantal karakters in $MSG ... heeft niets, maar dan ook niets met arguments of ARG_MAX te maken!
Misschien omdat assigment en echo builtins zijn. Laten we het eens proberen met een extern commando (ls). Het gaat me alleen om een eventuele foutmelding, vandaar de >/dev/null.
Bash:
1
2
$ ls -d $MSG > /dev/null
$

Geen melding. Zelfde verhaal (met vergelijkbare getallen) op mijn Debian machine. 't Ziet ernaar uit dat ARG_MAX geen effect heeft.
Hoeveel naampjes staan er nu eigenlijk in je MSG variabele? Anyway, een resultaat is heel eenvoudig te genereren:

code:
1
2
3
$ ls $(find .) > /dev/null
bash: /bin/ls: Argument list too long
$


Hierbij moet ik opmerken dat mijn home dir zo'n 150000 bestanden en directories bevat ...

Verwijderd

Verwijderd schreef op vrijdag 29 augustus 2008 @ 17:42:
[...]


Euh ... de wc -c leest het aantal karakters op je stdin, i.e. het aantal karakters in $MSG ... heeft niets, maar dan ook niets met arguments of ARG_MAX te maken!


[...]


Hoeveel naampjes staan er nu eigenlijk in je MSG variabele? Anyway, een resultaat is heel eenvoudig te genereren:

code:
1
2
3
$ ls $(find .) > /dev/null
bash: /bin/ls: Argument list too long
$


Hierbij moet ik opmerken dat mijn home dir zo'n 150000 bestanden en directories bevat ...
Wat jullie testen is de maximale grootte van de argumentlijst. Waar sam.vines het over had was de maximale grootte van een variabele. Je kan binnen Bash namelijk wel de variabele manipuleren zonder die mee te geven als argument.

Verwijderd

@flu pzor

Sam.vines heeft het hier toch echt over het max aantal argumenten:
sam.vimes schreef op donderdag 28 augustus 2008 @ 14:49:
[...]
Overigens is het IMHO een gevaarlijk idee om de inhoud van een file compleet in een variabele te stoppen en er dan mee te gaan slepen. Als de inhoud van de file meer is dan de maximale grootte van een command-line (ARG_MAX in limits.h, 4096 in POSIX, 32768 of zo in Linux) heb je al een probleem.
Ik ben het overigens helemaal met Sam eens. Vraag me nu alleen af of er een maximale variabele lengte bestaat? Stel je hebt een oneindige hoeveelheid geheugen beschikbaar, kan een variabele dan ook oneindig groot zijn? Anyway, we drijven OT.

-- update --

Hmmm, limits.h zegt;

code:
1
#define ARG_MAX       131072    /* # bytes of args + environ for exec() */


Dus toch lengte i.p.v. aantal :$. Overigens gaat het daarbij om de totale command-line, en je environment. Goede reden om je profile niet vol te stouwen met aliasen en functies ...

Wel weer wat geleerd vandaag ;)

[ Voor 19% gewijzigd door Verwijderd op 30-08-2008 01:01 . Reden: toevoeging ]


Verwijderd

Verwijderd schreef op zaterdag 30 augustus 2008 @ 00:48:
@flu pzor

Sam.vines heeft het hier toch echt over het max aantal argumenten:
Wat die zei was:
Overigens is het IMHO een gevaarlijk idee om de inhoud van een file compleet in een variabele te stoppen en er dan mee te gaan slepen. Als de inhoud van de file meer is dan de maximale grootte van een command-line (ARG_MAX in limits.h, 4096 in POSIX, 32768 of zo in Linux) heb je al een probleem.
Een variabele van zo'n grootte geeft dus alleen problemen als je hem meegeeft als argument. Niet als je er in bash bewerkingen op los laat.

  • sam.vimes
  • Registratie: Januari 2007
  • Laatst online: 07-01 22:10
Verwijderd schreef op vrijdag 29 augustus 2008 @ 17:42:
[...]
Euh ... de wc -c leest het aantal karakters op je stdin, i.e. het aantal karakters in $MSG ... heeft niets, maar dan ook niets met arguments of ARG_MAX te maken!
[...]
De wc -c inderdaad niet, maar de echo "$MSG" die ervoor staat wel degelijk! Dat commando expandeert namelijk naar een regel met lengte van bijna 200k.

Ik heb er nog even naar gekeken: het gedrag is wel afhankelijk van of het uitvoerende commando een intern bash commando (shell builtin) of een extern commando is.

Assignment (MSG=...) is een shell buitin, evenals echo. ls en /bin/echo zijn externe programma's.
Vergelijk het volgende maar (bash in Debian). Het find-commando levert in mijn home-dir 8209126 bytes output.
$ find|wc -c
8209126

Als ik dat commando interpoleer met een intern commando, gaat dat goed:
$ echo "$(find)" > /dev/null
$ MSG=$(find)
$

maar als ik het interpoleer met een extern commando, gaat het fout:
$ /bin/echo "$(find)" > /dev/null
bash: /bin/echo: Argumentenlijst is te lang
$ ls "$MSG"
bash: /bin/ls: Argumentenlijst is te lang
$

De grens ligt in de buurt van 131000:
$ /bin/echo $(perl -we 'print 1 for 1..131_071') >/dev/null
$ /bin/echo $(perl -we 'print 1 for 1..131_072') >/dev/null
bash: /bin/echo: Argumentenlijst is te lang
$

Daar hebben we dus de 131072 die _Danny_ vond.
Pagina: 1