How do I get only the PID, without any extra information, of a process running on port 3000?

Another possible solution:

lsof -t -i :<port> -s <PROTO>:LISTEN

For example:

# lsof -i :22 -s TCP:LISTEN
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd    1392 root    3u  IPv4  19944      0t0  TCP *:ssh (LISTEN)
sshd    1392 root    4u  IPv6  19946      0t0  TCP *:ssh (LISTEN)
# lsof -t -i :22 -s TCP:LISTEN
1392

Try this:

pid=$(fuser 3000/tcp 2>/dev/null)

(requires psmisc package)

Please note, that this is reliable only when run by the user root. Others user can only hope to find processes running with the same user.


Boring explanation for root only access with an example here.
Whatever the method used (fuser, ss, lsof, ...) they all end up matching the available list of process' descriptors to an available list of network connections (eg for tcp it's available in /proc/net/tcp).
For example, trying to get the pid using port 22/tcp (with 22 = 0x0016) would end up doing this kind of comparison:

Entry from /proc/net/tcp:
0: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 141408 1 000000000a9ac1b5 100 0 0 10 0

with:
dr-x------. 2 root root 0 May 14 17:59 /proc/358/fd lrwx------. 1 root root 64 May 14 17:59 /proc/358/fd/3 -> socket:[141408]

As this fd descriptor is only available for its user (which happen to be root in this example) or root, only that user or root can find out the pid is 358.


While lsof's -t is the simplest way to get the PID, lsof also has ways to select other fields using the -F option:

$ lsof -F'?'
lsof:   ID    field description
     a    access: r = read; w = write; u = read/write
     c    command name
     d    device character code
     D    major/minor device number as 0x<hex>
     f    file descriptor (always selected)
     G    file flaGs
     i    inode number
     k    link count
     K    task ID (TID)
     l    lock: r/R = read; w/W = write; u = read/write
     L    login name
     m    marker between repeated output
     n    comment, name, Internet addresses
     o    file offset as 0t<dec> or 0x<hex>
     p    process ID (PID)
     g    process group ID (PGID)
     P    protocol name
     r    raw device number as 0x<hex>
     R    paRent PID
     s    file size
     S    stream module and device names
     t    file type
     T    TCP/TPI info
     u    user ID (UID)
     0    (zero) use NUL field terminator instead of NL

With output like so (note that PID and file descriptors are always printed):

$ sudo lsof -F cg -i :22 -s TCP:LISTEN 
p901
g901
csshd
f3
f4

So if you wanted the process group ID instead of the PID, you could do:

$ sudo lsof -F g -i :22 -s TCP:LISTEN | awk '/^g/{print substr($0, 2)}'
901