Testowanie poprawności deskryptora pliku

12

Chciałbym, aby skrypt bash wyprowadzał dodatkowe informacje do deskryptorów plików (FD) większe lub równe 3, gdy są otwarte. Aby sprawdzić, czy FD jest otwarty, opracowałem następującą sztuczkę:

if (printf '' 1>&3) 2>&-; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

Jest to wystarczające dla moich potrzeb, ale jestem ciekawy, czy istnieje bardziej idiomatyczny sposób testowania, czy FD jest ważny. Jestem szczególnie zainteresowany tym, czy istnieje odwzorowanie fcntl(1)syscall na polecenie powłoki, które pozwalają na pobieranie FD flagami ( O_WRONLYoraz O_RDWRdo badania, czy FD jest zapisywalny, a O_RDONLYi O_RDWRdo testu czy FD jest czytelny).

Witiko
źródło

Odpowiedzi:

12

W ksh(zarówno wariantach AT&T, jak i pdksh) lub zshmożesz:

if print -nu3; then
  echo fd 3 is writeable
fi

Nie będą zapisywać niczego na tym fd, ale nadal sprawdzą, czy fd jest zapisywalny (używa fcntl(3, F_GETFL)) i w przeciwnym razie zgłoś błąd:

$ ksh -c 'print -nu3' 3< /dev/null
ksh: print: -u: 3: fd not open for writing

(do którego można przekierować /dev/null).

Wydaje bashmi się, że jedyną opcją jest sprawdzenie, czy odniesiesz dup()sukces jak w twoim podejściu, chociaż nie gwarantuje to, że fd jest zapisywalny (lub zadzwoni do zewnętrznego narzędzia ( zsh/ perl...), aby to zrobić fcntl()).

Zauważ, że w bash(jak większość powłok), jeśli użyjesz (...)zamiast tego {...;}, rozwinie to dodatkowy proces. Możesz użyć:

if { true >&3; } 2<> /dev/null

zamiast tego, aby uniknąć rozwidlenia (z wyjątkiem powłoki Bourne'a, w której przekierowywanie poleceń złożonych zawsze powoduje podpowłokę). Nie używaj :zamiast tego, trueponieważ jest to specjalne wbudowane narzędzie, więc spowodowałoby to zamknięcie powłoki, gdy bash jest w trybie zgodności z POSIX.

Możesz jednak skrócić go do:

if { >&3; } 2<> /dev/null
Stéphane Chazelas
źródło
@mikeserve, re: twoja edycja, z czym to jest <>? Powłoka nie będzie czytać ze swojego standardu, dlaczego miałbyś chcieć otworzyć ją w trybie odczytu + zapisu? Co masz na myśli przez to, co się stało z tym, co wewnętrzne? ?
Stéphane Chazelas,
7

W opisie użycia aplikacji POSIX znajdziesz następujące informacje:command

Czasami istnieją pewne zalety tłumienia specjalnych właściwości specjalnych wbudowanych elementów. Na przykład:

command exec > unwritable-file

nie powoduje przerwania nieinteraktywnego skryptu, dzięki czemu skrypt może sprawdzić status wyjścia.

Właśnie dlatego możesz po prostu:

if    command >&3
then  echo 3 is open >&3
else  ! echo 3 is not open
fi    2<>/dev/null

Lub...

{ command >&3
  printf %s\\n%.0d  string "0$(($??8:0))" >&"$(($??1:3))"
} 2<>/dev/null

Który napisze ciąg następnie przez \newline albo do stdout lub 3 i nadal przechodzą na niezerowym wyjścia kiedy 3 nie jest otwarty, ponieważ matematyka zrobić na $?nakręca braku do konwersji ósemkowy 08 do % przecinku ale obcina do niczego w ogóle ósemka 00 .

Lub...

command exec >&3 || handle_it

Ale jeśli używasz ksh93, możesz po prostu:

fds

Aby wyświetlić listę otwartych deskryptorów plików. Dodaj, -laby zobaczyć, gdzie idą.

mikeserv
źródło
3

Otwarte deskryptory plików można znaleźć w /proc/<pid>/fd. Aby na przykład wyświetlić listę otwartych deskryptorów plików bieżącej powłoki, możesz wydać ls -l /proc/$$/fdcoś, co powinno dać ci coś takiego:

total 0
lrwx------ 1 testuser testuser 64 jun  1 09:11 0 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 1 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 2 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:39 255 -> /dev/pts/3

Po otwarciu pliku za pomocą:

touch /tmp/myfile
exec 7</tmp/myfile

Powinien zostać wymieniony według nowego ls -l /proc/$$/fd:

lr-x------ 1 testuser testuser 64 jun  1 09:11 7 -> /tmp/myfile

Jeśli ponownie zamkniesz deskryptor pliku, używając exec 7>&-go, nie będzie /proc/$$/fdjuż na liście .

Lambert
źródło
2
Wszystko to jest dość specyficzne dla Linuksa. FWIW.
lcd047
1
Przetestowano go w systemie Linux, a także w systemie Solaris (10 i 11). Różnica polega na tym, że musisz użyć, pfiles <pid>aby zobaczyć, który deskryptor pliku jest podłączony do którego pliku podczas ls -lwyświetlania połączenia w systemie Linux.
Lambert
Podoba mi się zwartość [ -e /proc/$$/fd/3 ], ale wolę nie polegać na procfs, ponieważ jest on przestarzały w FreeBSD i ewentualnie innych systemach.
Witiko
1
Daje mi alternatywę użycia pfiles <pid>lub lsof -p <pid>sprawdzenia, które deskryptory plików są otwarte.
Lambert
1
/procw ogóle nie istnieje na OpenBSD. Na FreeBSD i NetBSD musi to być mountjawnie otwarte i /proc/<PID>nie ma podkatalogu fd.
lcd047
3

Twoja sztuczka wygląda uroczo; ale dla idiomatycznego sposobu zastanawiam się, dlaczego nie użyłeś:

if ( exec 1>&3 ) 2>&-
Janis
źródło
To jest rzeczywiście czystszy sposób.
Witiko
5
Tworzy to podpowłokę, która w większości powłok oznacza rozwiązywanie procesu. To nie gwarantuje, że fd można zapisać. Możesz użyć, { true >&3; } 2> /dev/nullaby ominąć widelec. Lub { command exec >&3; } 2> /dev/nulljeśli chcesz przekierować na standardowe wyjście.
Stéphane Chazelas
@Stephane; Sztuczka podpowłoki, którą wynalazł @Witiko, nie miała wpływu na deskryptory plików bieżącego środowiska podczas korzystania z przekierowania w celu uzyskania przekierowania. - Czy mógłbyś rozwinąć „zapisywalny fd” , o którym wspomniałeś?
Janis
2
{ true >&3; } 2> /dev/nullnie wpłynie również na bieżące środowisko i nie rozwidli się (z wyjątkiem powłoki Bourne'a). Mam na myśli, że (exec 1>&3) 2>&-zwróci true dla fd otwartego w trybie tylko do odczytu.
Stéphane Chazelas
1
execbycie specjalnym wbudowanym opuści powłokę, jeśli zawiedzie (dla bash, tylko w trybie zgodności z POSIX). command execzapobiega temu. truenie jest specjalnym wbudowanym. Zauważ, że execi command execwpływają na bieżące środowisko (dlatego powiedziałem, jeśli chcesz przekierować na standardowe wyjście ).
Stéphane Chazelas
-1

Jeśli interesuje Cię rozwiązanie o niskim rozwidleniu, aby móc go używać wielokrotnie, proponuję tę funkcję:

checkfd () {
    exec 2> / dev / null
    if exec> & 3; następnie
        exec 1> / dev / tty
        echo „fd3 OK”
    jeszcze
        echo „fd3 KO”
    fi
    exec 2> / dev / tty
}

A oto, co produkuje za pomocą zsh:

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3 OK
$
dan
źródło
W większości pocisków exec >&3zabije pocisk, gdy 3 nie jest otwarte.
mikeserv
Przynajmniej działa zshi bash. Czy możesz podać powłokę, na której execspowodowała awaria exit?
dan
Tak. W bashrobić set -o posixi spróbuj ponownie. W zsh... myślę, że to kwestia ustawienia env var POSIX_BUILTINSna wartość inną niż null - ale zapominam od razu. W każdym razie zshnie jest powłoką, która próbuje zachować zgodność z POSIX, a zatem jest zdecydowanie niestandardowa. Obie te powłoki unikają kompatybilności, co zdaniem niektórych jest wygodą.
mikeserv
Działa również na zwykłej powłoce Bourne'a.
dan
W bash, set -o posixpróba kończy się powodzeniem.
dan
-1

To wydaje się bardzo łatwe (patrz komentarze):

[ -r /proc/$$/fd/$FD ] && echo "File descriptor $FD is readable"
[ -w /proc/$$/fd/$FD ] && echo "File descriptor $FD is writable"

Jako dodatkowy ... Test [-r plik] nie wskazuje, czy jakieś dane faktycznie czekają na odczyt (/ dev / null przechodzi ten test (patrz komentarze)).

[ -r /proc/$$/fd/4 ] \
  && [ read -t 0.0001 -N 0 <&4 ] \
  && echo "Data is waiting to be read from file descriptor 4"

Wymagana jest niewielka liczba argumentu limitu czasu (read -t) lub dane wymagające obliczeń mogą zostać pominięte. Test czytelności ([plik -r]) jest wymagany lub polecenie odczytu zostanie zbombardowane, jeśli plik nie będzie czytelny. To nie odczyta żadnych danych, ponieważ liczba bajtów wynosi zero (odczyt -N 0).

Paweł
źródło
jeśli zamierzasz założyć system Linux, równie dobrze możesz rzucić okiem na /proc/<pid>/fdinfo/<fd>listę wszystkich trybów otwierania plików flags:- patrz tutaj . Dlatego twoja druga część (nawet po naprawieniu rażącego błędu): read -t .1 -N0 <&4nie powie, czy są dane do odczytu na fd 4: po prostu spróbuj 4</dev/null.
mosvy
I oczywiście [ -r /proc/$$/fd/$FD ]nie mówi ci, czy deskryptor pliku $FDjest czytelny, ale jeśli plik, z którego był otwarty, może być ponownie otwarty , z innym deskryptorem pliku, do czytania:exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy
-1

Pytanie jest dość stare - ale zresztą - dlaczego po prostu nie używać wbudowanych?

for i in {0..5} ; do if [ -t $i ]; then echo "$i is a valid FD"; else echo "$i is INVALID FD"; fi; done

Wynik:

0 is a valid FD
1 is a valid FD
2 is a valid FD
3 is INVALID FD
4 is INVALID FD
5 is INVALID FD

Tak więc, aby odpowiedzieć na pytanie - sugeruje:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi
Dimas
źródło
-tnie sprawdza, czy deskryptor pliku jest poprawny, ale czy jest podłączony do tty. Przygotuj a echo yup |do swojego skryptu, a powie, że 0 is INVALID FDchociaż w rzeczywistości jest to bardzo ważny fd, potok.
mosvy