Run a UNIX command for a limited amount of time

In the past couple of weeks, I’ve repeatedly found myself wishing for a UNIX command that would run a command for a while, and then stop it. For example, I might want to sample tcpdump output for 60 seconds, or tail the output of a log and search for a string to see if any errors occurred over a 5-minute period. So I begrudgingly set out to write one. And then I realized:

There is totally already a command that does this. It’s called timeout. Somehow, despite using Linux for about 15 years, I had never heard of it. (Not enough time writing shell scripts in bash? Is that actually a bad thing?) It’s part of coreutils.

For example, I ended up writing this gem:

sudo timeout 60 tcpdump -n net 191.247.228.0/24 and \
 dst port 123 -B 32000 | awk '{print $3}' | \
 cut -d "." -f 1-4 - | sort | uniq

Because it actually contains a lot of things I had to look up to get just right, I figure I’ll describe a bunch of those commands for my future-self:

sudo timeout

You can’t run timeout in front of a command with sudo, as I learned. It’ll launch the command with elevated privileges, but then try to kill it without them.

tcpdump -n net 191.247.228.0/24 and dst port 123 -B 32000

It annoys me that -n (don’t resolve hostnames) isn’t the default. Since name resolution is blocking, unless every host you’re resolving is on a network with functional, and fast, reverse-resolvers, you’re going to have a bad time.

I’m used to matching on host 1.2.3.4, but you can use net 1.2.3.0/24 or whatnot to match a network instead. You (this is the part I always get wrong) combine conditions with the and keyword (which seems so simple once you remember). dst port 123 matches traffic to port 123. (And even though it’s tcpdump, I’m using it to capture UDP port 123—NTP.)

-B 32000 is another fun one I just learned about. Ever seen this?

17 packets captured
37 packets received by filter
0 packets dropped by kernel

But with “packets dropped by kernel” as a non-zero number? It happens when there are so many packets coming in that they fall out of the buffer before tcpdump can process them. -B 32000 tries to set it to 32,000 kB. (The man page on my system doesn’t explain units, but this one does.)

awk ‘{print $3}’ | cut -d “.” -f 1-4 – |

My awk is pretty terrible, and it’s apparently quite powerful. But with a bunch of lines like this:

17:16:40.791327 IP 191.247.228.xxx.39440 > 10.252.153.236.ntp: NTPv3, Client, length 48

I just want the third column, with the IP. awk '{print $3}' achieves that. (It’s not zero-based. I get this wrong about 50% of the time.)

I use cut much less frequently. tcpdump shows the port number on the end, separated by a dot: “191.247.228.xxx.39440” is IP 191.247.228.xxx, port 39440. So I want to split on the dots, and print only columns 1-4.

-d "." sets the . as a delimiter, and -f 1-4 says to print fields 1-4. (Like awk, it starts with column 1.) The part I struggled with most, actually is remember the trailing - to tell it to read from the pipe, versus expecting a filename.

sort | uniq

This burns me all the time, until I came to just always use them together: uniq doesn’t really detect duplicates. Per the man page:

Note: ‘uniq’ does not detect repeated lines unless they are adjacent. You may want to sort the input first, or use ‘sort -u’ without ‘uniq’.
See for yourself:

$ echo -e "a\nb\na\na\nc" | uniq
a
b
a
c

(I should probably just do sort -u, but by now sort | uniq is etched into my brain.)

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax