Tips en Trucs 2011

Invoer van terminal of pipe

Soms wil je in een script weten of de invoer afkomstig is van een terminal (getypt door een gebruiker) of afkomstig is van een pipe (resultaten afkomstig van een andere opdracht). Dit lijkt eenvoudige, maar toch werd het zoeken op internet niet beloond met een oplossing. De volgende twee oplossingen kwamen tot stand na wat hersengymnastiek: via de stat opdracht en via informatie van het proc bestandssysteem.

De eerste oplossing

De eerste oplossing gebruikt de stat opdracht waarmee we het type bestand waaraan de standaard invoer is gekoppeld bepalen. Daar deze opdracht taalafhankelijke uitvoer produceert, zorgen we via een omweg voor standaard Engelstalige uitvoer:
language_tmp=$LANG
LANG=us_US.utf-8
Achteraf herstellen we de oorspronkelijke taal met:
LANG=$language_tmp
Daarna zoeken we uit wat is verbonden met de standaard uitvoer:
stdin="$(ls -l /dev/fd/0)"
stdin="${stdin/*-> /}"
Het bestand /dev/fd/0 is de standaard invoer, wat eigenlijk een koppeling (symbolic link) is. We gebruiken dus de opdracht ls om deze koppeling te achterhalen. In dit resultaat verwijderen we alles voor de tekens *->, wat ons uiteindelijk het gekoppelde bestand oplevert.

Nu kunnen we de opdracht stat gebruiken om het bestandstype van het gekoppelde bestand te bepalen:
ftype="$(stat --printf=%F $stdin)"
Nu kunnen we het bestandstype testen:
if   [[ "$ftype" == 'character special file' ]]; then 
	echo Terminal
elif [[ "$ftype" == 'regular file' ]]; then 
	echo Pipe: $stdin
else
	echo Unknown: $stdin
fi
Even testen met:
dany@Dany-HP:~> sh ckpipe.sh
Terminal
dany@Dany-HP:~> sh ckpipe.sh < ckpipe.sh
Pipe: /home/dany/ckpipe.sh

Het volledige script

#!/bin/bash

language_tmp=$LANG
LANG=us_US.utf-8

stdin="$(ls -l /dev/fd/0)"
stdin="${stdin/*-> /}"
ftype="$(stat --printf=%F $stdin)"

if   [[ "$ftype" == 'character special file' ]]; then 
	echo Terminal
elif [[ "$ftype" == 'regular file' ]]; then 
	echo Pipe: $stdin
else
	echo Unknown: $stdin
fi

LANG=$language_tmp

De tweede oplossing

De tweede oplossing gebruikt informatie uit het proc bestandssysteem. In het proc bestandssysteem verschijnen alle bestanden voor elk proces in de map /proc/PROCESS_ID/fd (voor het huidige proces kun je de map /proc/self/fd gebruiken):
dany@Dany-HP:~> ls -la /proc/self/fd
totaal 0
dr-x------ 2 dany users  0  4 nov 16:19 .
dr-xr-xr-x 7 dany users  0  4 nov 16:19 ..
lrwx------ 1 dany users 64  4 nov 16:19 0 -> /dev/pts/1
lrwx------ 1 dany users 64  4 nov 16:19 1 -> /dev/pts/1
lrwx------ 1 dany users 64  4 nov 16:19 2 -> /dev/pts/1
lr-x------ 1 dany users 64  4 nov 16:19 3 -> /proc/5013/fd
Zoals in de eerste oplossing bepalen we welk bestand de standaard invoer verzorgt:
stdin="$(ls -l /proc/self/fd/0)"
stdin="${stdin/*-> /}"
Daarna testen we of de naam van het bestand gekoppeld is aan een /dev/pts bestand:
if [[ "$stdin" =~ ^/dev/pts/[0-9] ]]; then
	echo Terminal
else
	echo Pipe: $stdin
fi
Testen kan op een gelijkaardige manier:
dany@Dany-HP:~> sh ckpipe2.sh
Terminal
dany@Dany-HP:~> sh ckpipe2.sh < ckpipe2.sh
Pipe: /home/dany/ckpipe2.sh

Het volledige script

#!/bin/bash

stdin="$(ls -l /proc/self/fd/0)"
stdin="${stdin/*-> /}"

if [[ "$stdin" =~ ^/dev/pts/[0-9] ]]; then
	echo Terminal
else
	echo Pipe: $stdin
fi