3

I can open a file descriptor explicitly the normal way:

$ ls -lh /dev/fd/
total 0
lrwx------ 1 tavianator users 64 Jul 10 11:06 0 -> /dev/pts/6
lrwx------ 1 tavianator users 64 Jul 10 11:06 1 -> /dev/pts/6
lrwx------ 1 tavianator users 64 Jul 10 11:06 2 -> /dev/pts/6
lr-x------ 1 tavianator users 64 Jul 10 11:06 3 -> /proc/31288/fd
$ exec 3<foo
$ ls -lh /dev/fd/
total 0
lrwx------ 1 tavianator users 64 Jul 10 11:07 0 -> /dev/pts/6
lrwx------ 1 tavianator users 64 Jul 10 11:07 1 -> /dev/pts/6
lrwx------ 1 tavianator users 64 Jul 10 11:07 2 -> /dev/pts/6
lr-x------ 1 tavianator users 64 Jul 10 11:07 3 -> /home/tavianator/foo
lr-x------ 1 tavianator users 64 Jul 10 11:07 4 -> /proc/31334/fd

So far so good. zsh doesn't seem to support two-digit file descriptor syntax like 10<foo, but it does support a variable substitution syntax {fd}<foo:

$ fd=10
$ exec {fd}<foo
$ ls -lh /dev/fd/
total 0
lrwx------ 1 tavianator users 64 Jul 10 11:08 0 -> /dev/pts/6
lrwx------ 1 tavianator users 64 Jul 10 11:08 1 -> /dev/pts/6
lr-x------ 1 tavianator users 64 Jul 10 11:08 11 -> /home/tavianator/foo
lrwx------ 1 tavianator users 64 Jul 10 11:08 2 -> /dev/pts/6
lr-x------ 1 tavianator users 64 Jul 10 11:08 3 -> /home/tavianator/foo
lr-x------ 1 tavianator users 64 Jul 10 11:08 4 -> /proc/31413/fd

But hold on, why is fd 11 open instead of 10?

  • s/normal way/Linux way/. Here's what happens on OpenBSD: ls: /proc/self/fd: No such file or directory. FWIW. – Satō Katsura Jul 10 '17 at 15:54
  • @SatōKatsura Fair enough, I replaced that with /dev/fd/ so it's more widely reproducible. – Tavian Barnes Jul 10 '17 at 15:56
  • Still no go, on *BSD /dev/fd is populated with actual devices. But this has nothing to do with the gist of your question. – Satō Katsura Jul 10 '17 at 16:00
  • {fd}<foo will set variable fd, not use its value. – meuh Jul 10 '17 at 16:43
  • @meuh Ah okay! That doesn't seem to happen consistently though, fd=30; ls -lh /dev/fd/ {fd}<foo; echo $fd prints 30 but fd=30; true {fd}<foo; echo $fd prints 11, 12, etc. – Tavian Barnes Jul 10 '17 at 17:00
3

Because that's how ZSH is written. ZSH by default duplicates a file descriptor to fd 10:

$ PS1='%% ' zsh -f
% lsof -p $$ | grep 10u
zsh     29192 jhqdoe   10u   CHR  136,0       0t0         3 /dev/pts/0
% 

And subsequent fd-related code in Src/exec.c calls movefd

/**/
static void
addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
      char *varid)
{
    int pipes[2];

    if (varid) {
        /* fd will be over 10, don't touch mfds */
        fd1 = movefd(fd2);
        if (fd1 == -1) {
            zerr("cannot moved fd %d: %e", fd2, errno);
            return;

which over in Src/utils.c does the duplicate-to-the-next-available-above-10-which-is-already-taken-by-default-so-the-first-you'll-see-is-11 thing:

movefd(int fd)
{
    if(fd != -1 && fd < 10) {
#ifdef F_DUPFD
        int fe = fcntl(fd, F_DUPFD, 10);
#else
        int fe = movefd(dup(fd));
#endif

My zsh according to strace is using the fcntl code path, though I suspect from the comments that either fcntl(... or movefd(dup(... will result in new fd starting at 11; 10 is not available as zsh by default holds a duplicate at that number.

All the {somelabel} does is to obtain the lowest available file descriptor greater than 10; this could be 11 or may instead be some higher number depending on what else the shell already has open:

% exec {foo}>asdf
% echo $foo
11
% exec {quer}>asdf
% echo $quer
12
...
  • Ah okay, perhaps I have misinterpreted the intent of the {fd}<file syntax? 3<file, 4<file, 5<file etc. all do what I expect. Can I get that behaviour for fds bigger than 9 in some way? – Tavian Barnes Jul 10 '17 at 16:27
  • Not really, but you can get whatever fd number happens to be associated with {foo} via $foo – thrig Jul 10 '17 at 16:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.