12

So today, April 23rd 2015, the Internet Assigned Numbers Authority had decreed the use of port 6379 to Redis, a frabjous day indeed!

I wish to com·mem·o·rate this splendid occasion by adding the following line to my /etc/services file:

redis     6379/tcp

What would be the best way to go about it? By best I mean, of course, the following:

  1. Needless to say, the new line should be inserted in its proper place (i.e.g. under the Assigned Numbers block, right after gnutella-rtr 6347/udp on my system)
  2. I've considered the use of various text editors, but it feels out of place
  3. Ideally, the solution should be a copy-pastable one-liner
  4. I can envision the awk script that could do that but I'm looking for something more, a certain je ne sais quoi

Update re @Markus' sed proposal: I'm afraid the problem would be applying this "patch" on other systems that do not necessarily have the same /etc/services file so, expanding on point #1 above, the solution must ensure that regardless the specifics of to-be-preceding service in the file, order is kept.

Update 2: a few points that seem important to state - a) while not mandatory, the solution's length (or lack of rather) is certainly an important part of its elegance (similarly for external dependencies [i.e. lack of these]); b) I/we assumed that /etc/services is sorted, but it would be interesting to see what happens when it isn't; c) assume that you have root privileges and be careful with that rm / -rf command.

0

7 Answers 7

9
+25

A single line sort that puts it in the right place:

echo -e "redis\t\t6379/tcp" | sort -k2 -n -o /etc/services -m - /etc/services

2
  • 1
    I knew I should have gone in-house from the start ;) Seriously though - there will be no favoritism at the end of the day and only the best answers can win. Definitely the shortest answer - extra points for that. The -m flag gives me the hibbie-jibbies a little - what if /etc/services isn't sorted to begin with (i.e. non-standard, valid doubt against all answers BTW). In any case, I suggest you edit the answer and remove the sudo - no one bothered had with that anyway ;) Apr 26, 2015 at 22:20
  • 2
    The -m is just to make sure those comment lines do not move. A non-sorted services will stay non-sorted. sudo? Where did you see that? :)
    – Ofir Luzon
    Apr 27, 2015 at 6:08
3

Nothing in the rules against answering my own question - this one uses Redis exclusively:

cat /etc/services | redis-cli -x SET services; redis-cli --raw EVAL 'local s = redis.call("GET", KEYS[1]) local b, p, name, port, proto; p = 1 repeat b, p, name, port, proto = string.find(s, "([%a%w\-]*)%s*(%d+)/(%w+)", p) if (p and tonumber(port) > tonumber(ARGV[2])) then s = string.sub(s, 1, b-1) .. ARGV[1] .. "\t\t" .. ARGV[2] .. "/" .. ARGV[3] .. "\t\t\t# " .. ARGV[4] .. "\n" .. string.sub(s, b, string.len(s)) return s end until not(p)' 1 services redis 6379 tcp "remote dictionary server" > /etc/services

Formatted Lua code:

local s = redis.call("GET", KEYS[1])
local b, p, name, port, proto

p = 1
repeat
    b, p, name, port, proto = string.find(s, "([%a%w\-]*)%s*(%d+)/(%w+)", p)
    if (p and tonumber(port) > tonumber(ARGV[2])) then
        s = string.sub(s, 1, b-1) .. ARGV[1] .. "\t\t" .. ARGV[2] 
        .. "/" .. ARGV[3] .. "\t\t\t# " .. ARGV[4] .. "\n" 
        .. string.sub(s, b, string.len(s))
        return s
    end 
until not(p)

Note: a similar challenge (https://gist.github.com/jorinvo/2e43ffa981a97bc17259#gistcomment-1440996) had inspired this answer. I chose a pure Lua script approach instead of leveraging Sorted Sets... although I could :)

2

An idempotent awk one-liner that inserts 6379 in order would be:

awk -v inserted=0 '/^[a-z]/ { if ($2 + 0 == 6379) { inserted=1 }; if (inserted == 0 && $2 + 0 > 6379) { print "redis\t\t6379/tcp"; inserted=1 }; print $0 }' /etc/services > /tmp/services && mv /tmp/services /etc/services
1
  • Perfect - my first choice atm :) Apr 24, 2015 at 21:19
2

Real men don't use sed/awk :)

TMP_SERVICES=/tmp/services.$RANDOM

while read line
do
  printf %b "$line\n" >> $TMP_SERVICES
  if [[ $line == *"6347/udp"* ]]; then
    printf %b "redis\t\t6379/tcp\n" >> $TMP_SERVICES
  fi
done<"/etc/services"

mv -fb $TMP_SERVICES /etc/services
3
  • Well said, but what about real fanboys? ;) My only concern - you're specifically targeting 6347/udp which could be iffy on some system. Apr 28, 2015 at 13:51
  • 1
    Idk, shouldn't /etc/services be standardized? At least to a point where 6347 is on all systems... P.S surprised to see nobody came up with the curl -o my/script.sh | bash approach yet :)
    – cryptid
    Apr 28, 2015 at 16:15
  • It's standardized... like electrical sockets are, and anyone can edit her/his own anyway Apr 28, 2015 at 18:54
2

How about a python script, Itamar?

It works on the notion of extracting the port number (called the index in the code) and if we are above 6378 but have not yet printed our Redis line, print it, then mark that sentinel true and just print all lines (including the one we are on) after.

#!/usr/bin/python
lines = open("/etc/services").readlines()
printed=False
for line in lines:
    if printed:
        print line.rstrip()
        continue
    datafields = line.split()
    if line[0] == "#":
        print line.rstrip()
    else:
        datafields = line.split()
        try:
            try:
                index,proto = datafields[1].split("/")
                index = int(index)
            except:
                index,proto = datafields[0].split("/")
                index = int(index)
            if index > 6378:
                if not printed:
                    print "redis           6379/tcp    #Redis DSS"
                    printed = True
            print line.rstrip()
        except:
            print datafields
            raise

The relevant section on my file for comparison:

gnutella-rtr    6347/tcp    # gnutella-rtr
#                          Serguei Osokine <[email protected]>
#               6348-6381  Unassigned
redis           6379/tcp    #Redis DSS
metatude-mds    6382/udp    # Metatude Dialogue Server
metatude-mds    6382/tcp    # Metatude Dialogue Server

Notice the line above Redis is a range. Short of breaking the range this is a workable solution for me. You could break the range but IMO this works just fine. Splitting the range seems a bit much for a simple, elegant script. Especially considering the likelihood most services files don't have the unassigned ranges listed (this is on OS X) - and that they are in a comment anyway.

UPDATE

If you don't care about the local file and it's comments, this gets you all currently assigned ports which are not Reserved or Discard-ed: curl -s http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv| awk -F',' '$4!~/(Discard|Unassigned|Reserved)/ && $1 && $2+0>0 && $1!~/FIX/ {printf "%-16s\t%s/%s\t#%s\n", $1,$2,$3,$4}' > /etc/services

The FIX test is because some of those lines have embedded newlines - which can be a pain in awk.

3
  • Heh, one could implement it in Go, then running the compiled binary could be a one-liner. ;) Apr 24, 2015 at 5:53
  • 1
    This one's definitely the most robust and extensible solution - the only drawback is that uses the comical scripting language that, while being current favorite coding choice, is a dependency that I'm reluctant to admin into the scope of this project. A compiled binary, be it in Go or ANSI C, while being the most peformant for sure, does not resonate well with the spirit of the, ehm, challenge. Apr 24, 2015 at 21:23
  • well it is a dependency you'll find already installed on virtually every modern OS which uses /etc/services. ;) Awk isn't quite the same every where either - which is why I now have awk scripts for OS X (which uses "the one true awk") and different ones for Linux (and in some cases I install a different awk to handle it in Linux). :/ The simple stuff is usually the same but once you get into the more complex stuff it can break. Same actually goes for sed as well. Apr 28, 2015 at 12:03
1
sed -i '/6347\/udp/a redis     6379\/tcp' /etc/services

good luck!

update

sed -i '/6347\/udp/a redis \t\t 6379\/tcp' /etc/services

looks better ...

update 2

lol

sed -i ''"$(echo $(echo $(grep -n $(awk {'print$2'} /etc/services | awk -F "/" '$1<6379'{'print$1'} | tail -1) /etc/services | awk -F ':' {'print$1'}|tail -1) + 1)|bc)"'i redis \t\t 6379\/tcp' /etc/services
1
  • Heya @Markus - are you the same Markus that I'm thinking of? ;) That's basically what I had in mind but please see the update to the question. Apr 23, 2015 at 22:20
1

I like the in-place subsitution of sed, so I combine it with an awk search of the next line in services

R="redis\t\t6379/tcp\t\t\t# data structure server" N=$(awk '{if($2+0 == 6379) exit(1);if ($2+0 > 6379) {print $0;exit(0)}}' /etc/services) && sed -i "s~$N~$R\n$N~" /etc/services
1
  • 1
    Enough sed! Awksome :) Apr 26, 2015 at 22:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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