How to get my own IP address and save it to a variable in a shell script?
A word of caution: There are a lot of «works on my system» answers here that may not be portable to other environments. You have to decide if you want something that works for you or something that can be shared. Every system has multiple IPs. A portable solution answers the Q: «what IP do I use to reach blah?» A works on my system solution answers the Q: «what’s my IP?» with the A: «I think you mean this one…» This should be portable unix.stackexchange.com/a/402160/9745
25 Answers 25
I believe the «modern tools» way to get your ipv4 address is to parse ip rather than ifconfig , so it’d be something like:
ip4=$(/sbin/ip -o -4 addr list eth0 | awk '' | cut -d/ -f1) ip6=$(/sbin/ip -o -6 addr list eth0 | awk '' | cut -d/ -f1)
ip is available on all the Red Hat and Fedora distros I’ve used. ip is part of the iproute2 package (linuxfoundation.org/collaborate/workgroups/networking/iproute2). ifconfig and route are supposedly deprecated, although they continue to be used by a lot of people, particularly in scripts. ip is much more parsable, in my opionion.
ip is also available on all Debian and Debian based distros I’ve seen. It’s part of the iproute package which is marked as important.
This is absolutely how it should be done, I would downvote the other answers for their use of ifconfig. ip is part of the iproute2 package which is many a distro by default. It’s in most good repos to. en.wikipedia.org/wiki/Iproute2
This is an excellent answer! The combination of awk and cut is mildly amusing to me, here’s a possible alternative which may not actually be better: ip -o -4 addr show eth0 | awk ‘< split($4, ip_addr, "/"); print ip_addr[1] >‘
Why not simply do IP=$(hostname -I) ?
@amphibient: Yeah, that’s standard. Per its documentation, it returns all of the host’s configured IP addresses (except loopback and link-local addresses). The people seeing only one IP address must be running it on a host that only has one non-loopback interface.
Hostname -I still allows for ambiguity. I have a lot of docker networks and hence I see: 192.168.0.33 192.168.1.106 192.168.16.1 172.23.0.1 172.17.0.1 172.24.0.1 172.26.0.1 172.18.0.1 172.27.0.1 2a02:8108:41bf:c4b0::3c00 2a02:8108:41bf:c4b0:94ab:8d54:b734:1cb0 2a02:8108:41bf:c4b0:d1bb:4aa7:7cce:b458 Only the first IP in this list is the one that I want, yet can I trust the order to always be correct?
It’s not so easy if you want to take into account wlan and other alternative interfaces. If you know which interface you want the address for (e.g., eth0, the first Ethernet card), you can use this:
ip="$(ifconfig | grep -A 1 'eth0' | tail -1 | cut -d ':' -f 2 | cut -d ' ' -f 1)"
In other words, get me the network configuration information, look for eth0 , get that line and the next one ( -A 1 ), get only the last line, get the second part of that line when splitting with : , then get the first part of that when splitting with space.
I added an additional grep to your code to ignore any interface with «eth0:» in it; this now works as I expect (giving only the «eth0″ IP address and not any sub-interfaces (eth0:0, eth0:1, etc.): ip=»$(ifconfig | grep -v ‘eth0:’ | grep -A 1 ‘eth0’ | tail -1 | cut -d ‘:’ -f 2 | cut -d ‘ ‘ -f 1)»
If you want the address of an interface, the easiest way is to install moreutils then:
anthony@Zia:~$ ifdata -pa br0 172.16.1.244
ifdata answers pretty much every question you’d be tempted to parse ifconfig output for.
If you want to find out your IP address as the outside sees it (beyond any NAT, etc.), there are plenty of services that’ll do it. One is fairly easy:
anthony@Zia:~$ curl ifconfig.me 173.167.51.137
Oh goodness, thank you for this answer. Never heard of moreutils before. I was seriously considering writing my own little C program to do exactly that.
Some parsing may still be nesssisary if you don’t know your default interface name: ifdata -pa $(ip route list | grep default | awk ‘
I don’t mean to be a jerk, but there really is a right way and this is it. You trim the output of ip route to get only the source IP. Depending on what IP you are trying to reach, «my own ip address» (OP’s words) will be different. If you care about reaching the public internet, using Google’s 8.8.8.8 DNS server is pretty standard. So.
The short answer is:
ip route get 8.8.8.8 | sed -n '/src/'
Here’s the detailed explanation
If I want the ip I use to reach the internet, I use this:
pi@et3:~ $ ip route get 8.8.8.8 | sed -n '/src/' 10.55.0.200
If I want the ip I use to reach something on my VPN, I use this:
pi@et3:~ $ ip route get 172.31.0.100 | sed -n '/src/' 172.29.0.9
This next one is really just for illustrative purposes. But, it should work on any Linux system. So, you can use this to demonstrate that, yes, all machines have multiple IP addresses at all times.
If I wanted the ip I use to reach myself, I would use this:
pi@et3:~ $ my_ip=$(getent hosts $(cat /etc/hostname) | awk '') pi@et3:~ $ ip route get $my_ip | sed -n '/src/' 127.0.0.1
More about that sed command
First let me say that when choosing unix tools, you try to choose the tools that require the fewest pipes. So, while some answers will pipe ifconfig to grep to sed to head , that is rarely ever necessary. When you see it, it should raise a red flag that you are taking advice from someone with little experience. That doesn’t make the «solution» wrong. But, it probably could use some streamlining.
I have chosen sed because it is more terse than the same workflow in awk . (I have since an awk example below.) I don’t think any other tool but those 2 would be appropriate.
sed # the sed executable located via $PATH -n # no output unless explicitly requested ' # begin the command space /src/ # regex match the string 'src' < # begin a block of commands ** s/ # begin a substitution (match) .*src * # match anything leading up to and including src and any number of spaces \([^ ]*\) # define a group containing any number of non spaces .* # match any trailing characters (which will begin with a space because of the previous rule). / # begin the substitution replacement \1 # reference the content in the first defined group / # end the substitution p # print (explicitly, remember) the result ; # designate the end of the command q # quit ># end the block of commands ' # end the command space ** all of which will be performed "on match" - otherwise only the first command to following the match would be performed "on match" - any other commands would be performed whether there was a match or not
I used to use sed -n ‘/src/’ but a commenter pointed out that some systems have trailing data after the src field.
Using awk
ip route get 8.8.8.8 | \ awk '' # or ip route get 8.8.8.8 | \ awk '>>'
More about my network
My ifconfig shows that I have tun0 for my VPN and eth0 for my lan.
pi@et3:~ $ ifconfig eth0: flags=4163 mtu 1500 inet 10.55.0.200 netmask 255.255.252.0 broadcast 10.55.3.255 inet6 fe80::71e6:5d7c:5b4b:fb25 prefixlen 64 scopeid 0x20 ether b8:27:eb:b2:96:84 txqueuelen 1000 (Ethernet) lo: flags=73 mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10 loop txqueuelen 1 (Local Loopback) tun0: flags=4305 mtu 1500 inet 172.29.0.9 netmask 255.255.255.255 destination 172.29.0.10 inet6 fe80::3a8e:8195:b86c:c68c prefixlen 64 scopeid 0x20 unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 100 (UNSPEC) wlan0: flags=4099 mtu 1500 ether b8:27:eb:e7:c3:d1 txqueuelen 1000 (Ethernet)
ip route get 8.8.8.8 | sed -n ‘/src/‘ gave me 10.1.2.3 uid 1002 . So I add to append | awk » to keep only the IP address.
@wisbucky I never pipe sed/awk/grep to each other in recorded code. (I will use it on the command line when I’m in a hurry, but you will never find it written to disk other than in my ~/.bash_history) I updated the answer to use awk and it solves this issue.
Note (as suggested elsewhere) jq -j route get 8.8.8.8 | jq .[].prefsrc involves just as few pipes but is much less vulnerable to subtle variations in output format and much easier to understand than the sed recipe. The only wrinkle is that busybox’s version of ip doesn’t support JSON output.
To get IPv4 and IPv6 addresses, and not assume the main interface is eth0 (these days em1 is more common), try:
ips=$(ip -o addr show up primary scope global | while read -r num dev fam addr rest; do echo $; done)
- -o uses the one-line output format, which is easier to process with read , grep , etc.
- up excludes devices that aren’t active
- scope global excludes private/local addresses such as 127.0.0.1 and fe80::/64
- primary excludes temporary addresses (assuming you want an address that doesn’t change)
This has to be the best answer because 1) it doesn’t assumes a device name, 2) it uses ‘ip’ as opposed to old school ‘ifconfig’, and 3) it supports ipv6 as well.
I agreed, it the best start for most use cases. You can pass -4 / -6 to filter address types. You can also easily get other interface parameters. Personally, I dislike while loop and I prefer grep -o ‘inet6\? [0-9a-f\.:]*’ | cut -f 2 -d ‘ ‘
Depends what you mean by own IP address. Systems have IP addresses on several subnets (sometimes several per subnet), some of which IPv4, some IPv6 using devices like ethernet adapters, loopback interfaces, VPN tunnels, bridges, virtual interfaces.
I you mean the IP address by which another given device may reach your computer, you have to find out which subnet that is, and which version of IP we’re talking about. Also, bear in mind that because of NAT performed by firewall/routers, the IP address of an interface may not be the same as a remote host sees an incoming connection from your computer coming from.
When there is fancy source routing or per protocol/port routing it can be difficult to find out which interface would be used to talk to one remote computer over a given protocol and even then, there’s no guarantee that the IP address of that interface may be directly addressable by the remote computer wanting to establish a new connection to your computer.
For IPv4 (probably works for IPv6 as well), a trick that works in many unices including Linux to find out the IP address of the interface used to reach a given host is to use a connect(2) on a UDP socket and use getsockname():
For instance, on my home computer:
perl -MSocket -le ' socket(S, PF_INET, SOCK_DGRAM, getprotobyname("udp")); connect(S, sockaddr_in(1, inet_aton("8.8.8.8"))); print inet_ntoa((sockaddr_in(getsockname(S)))[1]);'
Would be used to find out the IP address of the interface via which I would reach 8.8.8.8 (google’s DNS server). It would return something like «192.168.1.123» which is the address of the interface for the default route to the internet. However, google wouldn’t see a DNS request from my machine as coming from that IP address which is a private one, as there’s NAT performed by my home broadband router.
connect() on a UDP socket doesn’t send any packet (UDP is connection-less), but prepares the socket by querying the routing table.
On Linux and with recent versions of iproute2 and a json parsing tool like jq , you should be able to get something similar with:
ip -j route get 8.8.8.8 | jq -r '.[].prefsrc' # IPv4 ip -6 -j route get 2001:4860:4860::8888 | jq -r '.[].prefsrc' # IPv6
Beware the openvpn when configured to force traffic through the tunnel adds two routes for 0.0.0.0/1 and 128.0.0.0/1 instead of one default (0.0.0.0/0) route, so the approaches that query the default route don’t work in that case.