Piping in awk scripts

awkio-redirection

I'm trying to write a ls wrapper that uses awk to parse the output of ls -lhF. Right now I've split the program into two files – my_ls.sh and my_ls.awk. my_ls.sh's only purpose is to pipe the output of ls -lhF into my_ls.awk. It looks like:

#!/bin/bash
ls -lhF "$@" | my_ls.awk

I was wondering if there was any way to read the output of ls -lhF through the awk script itself.

EDIT: My main purpose is to write a script which shows the current directory contents in the form of a nice tree. A draft version of my_ls.awk would look like:

#!/usr/bin/awk -f

( NF >= 9 ) {
    print "|-- [" $5 "] " $9
}

This is where I've reached so far.

Best Answer

I'll join the other advice that you shouldn't parse the output of ls, so this is a bad example. But as a more general matter, I would include the awk script directly in the shell script by passing it as an argument to awk.

#!/bin/bash
ls -lhF "$@" | awk '
    ( NF >= 9 ) {
        print "|-- [" $5 "] " $9
    }'

Note that if the awk script must include the ' (single quote) character, you need to quote it: use '\'' (close single quote, literal single quote, open single quote).

To avoid having to quote, you can use a here document instead. But it's awkward because you can't use standard input both to feed input to awk and to feed the script. You need to use an additional file descriptor (see When would you use an additional file descriptor? File descriptors & shell scripting).

#!/bin/bash
ls -lhF "$@" | awk -f /dev/fd/3 3<<'EOF'
( NF >= 9 ) {
    print "|-- [" $5 "] " $9
}
EOF

Inside awk, you can read input from another command using the getline function and the pipe construct. It's not the way awk is primarily designed to be used, but it can be made to work. You need to quote the file name arguments for the underlying shell, which is highly error-prone. And since the text to be processed doesn't come from the expected sources (standard input or the files named on the command line), you end up with all the code in the BEGIN block.

#!/usr/bin/awk -f
BEGIN {
    command = "ls -lhF"
    for (i = 1; i <= ARGC; i++) {
        arg = ARGV[i];
        gsub("'", "'\\''", arg);
        command = command " '" arg "'";
    }
    ARGC = 0; for (i in ARGV) delete ARGV[i];
    while ((command | getline) > 0) {
        if (NF >= 9) { print "|-- [" $5 "] " $9 }
    }
}

In short, use a shell for what it's good at (such as piping commands together), and awk for what it's good at (such as text processing).

Related Question