Score:6

How can I store the IP and Port that are separated by ":" to two variables?

cn flag

I want to extract the IP and Port from a variable. I have tried to use this:

ADDR=1.2.3.4:12345
IP=${ADDR##:}
PORT=${ADDR%:}

However, this assigns the entire address to both variables.

How can I store the the IP and Port to two variables?

Marco avatar
br flag
try `IP=${ADDR##*:}`
DenisZ avatar
cn flag
Thanks @Marco , that works. Do you know why it works without * on other machine, and they are both very similar 20.04 Ubuntu
BeastOfCaerbannog avatar
ca flag
@DenisZ What is different in the other machine you mention? Does it have a different OS? A different OS version? Are you using a different shell?
DenisZ avatar
cn flag
Thanks @BeastOfCaerbannog All the same Ubutnu 20.04, but I went through script again, but was working by accident, when I tried it manually also didn't match, so my bad.
小太郎 avatar
us flag
Note that this assumes IPv4, since IPv6 will have colons in the IP address itself
Score:15
vn flag

You got it wrong, has to be like this:

ADDR=1.2.3.4:12345
IP=${ADDR%:*}
PORT=${ADDR##*:}

Since there is only one matching occurrence of : for IPv4, in this case it doesn't matter if you match first or last occurrence. However, in case of IPv6, it's best to match last occurrence for port. In addition, it seems you accidentally switched IP and PORT.

Also see here: Bash cheatsheet and Bash Hacker wiki

In short, substring removal works like this:

  • ${variable#pattern}: Remove from the beginning of string, first match
  • ${variable##pattern}: Remove from the beginning of string, last match
  • ${variable%pattern}: Remove from the end of string, first match
  • ${variable%%pattern}: Remove from the end of string, last match

It can be necessary to include wildcard (*) in the pattern if you want to match only part of a word, that is not separated by whitespace.

DenisZ avatar
cn flag
Thanks @Artur. This works. If no port listed than PORT is then also IP, Should I use different method to get PORT null if no ':'
Artur Meinild avatar
vn flag
Yes, that would be a condition you should handle accordingly.
DenisZ avatar
cn flag
but how to get PORT variable in no ':' in variable?, btw please update your `${VARIABLE%%PATTERN}` for end of string match
Artur Meinild avatar
vn flag
Please ask a new question if you have a new question. Thank you.
Raffa avatar
jp flag
+1 A suitable expansion as well for this task is the replacement i.e. `${Var//pattern/replacement}` ... e.g. `${ADDR//*:/}` and `${ADDR//:*/}` ... It might be handy if you want to keep the `:` with the port e.g. `${ADDR//*:/:}`
user1686 avatar
us flag
It's not guaranteed that there will be only one `:`, as IPv6 addresses will always have more than one (e.g. `[2001:db8::1234]:80`), so first vs last does matter. Your solution is the only one that works for this case, although it needs `${ADDR##:*}`.
Artur Meinild avatar
vn flag
@user1686 My answer was written under the assumption that it is for IPv4. I agree that IPv6 is a different scenario, but since this isn't mentioned at all, I assumed it wasn't relevant here. But good point.
Score:7
jp flag

Since your string has no spaces and no shell globs i.e.:

addr="1.2.3.4:12345"

(It's a good practice to quote your string BTW)

You can simply populate an array with the IP and the PORT like so:

a=(${addr//\:/ })

That will replace each : in the string with a space inside the () and voila! it's an array now ... That would work for things like MAC addresses as well, but expect more array elements.

Notice: This will work for IPv4 … For IPv6, you can do something like this instead:

addr="[1fff:0:a88:85a3::ac1f]:8001"
addr="${addr/\[/}"
a=(${addr/\]\:/ })

Alternatively: You can automate this to handle both IPv4 and IPv6 with e.g. a condition like this:

if [[ "$addr" =~ [\:]{2,} ]]
  then
  # It's IPv6 ... Do this
  addr="${addr/\[/}"
  a=(${addr/\]\:/ })
  else
  # It's IPv4 ... Do this instead
  a=(${addr//\:/ })
  fi

And then, you can call and print the array elements like so:

echo -e "IP: ${a[0]}\nPORT: ${a[1]}"

Then simply use it like so:

$ addr="1.2.3.4:12345"
$ a=(${addr//:/ })
$ echo -e "IP: ${a[0]}\nPORT: ${a[1]}"
IP: 1.2.3.4
PORT: 12345

Or to handle empty or no PORT like so:

$ addr="1.2.3.4:" # or even addr="1.2.3.4"
$ a=(${addr//:/ })
$ echo -e "IP: ${a[0]}\nPORT: ${a[1]:-EMPTY}"
IP: 1.2.3.4
PORT: EMPTY

As far as manipulation and evaluation goes ... Please see the demonstration below:

$ addr="1.2.3.4:12345"
$ # Populating an array "a" with the elements around the ":" from the string in "$addr"
a=(${addr//:/ })
$ # Assigning array "a" elements to parameters
ip="${a[0]}"
port="${a[1]}"
$ # Assigning array "a" length(number of elements) to a parameter
len="${#a[@]}"
$ # Printing array length
echo "${#a[@]}"
echo "$len"
2
2
$ # Printing all array "a" elements
echo "${a[@]}"
1.2.3.4 12345
$ # Printing array "a" elements by index
echo "IP: ${a[0]} and PORT: ${a[1]}"
IP: 1.2.3.4 and PORT: 12345
$ # Printing array elements from parameters
echo "IP: $ip and PORT: $port"
IP: 1.2.3.4 and PORT: 12345
$ # Evaluating by array length
[ "${#a[@]}" == "2" ] && echo "IP: $ip and PORT: $port" || echo "IP: $ip and PORT: NA"
[ "$len" == "2" ] && echo "IP: $ip and PORT: $port" || echo "IP: $ip and PORT: NA"
IP: 1.2.3.4 and PORT: 12345
IP: 1.2.3.4 and PORT: 12345
$ # Evaluating by array element/parameter string length "-z"(zero)
[ -z "${a[1]}" ] && echo "IP: ${a[0]} and PORT: NA" || echo "IP: ${a[0]} and PORT: ${a[1]}"
[ -z "$port" ] && echo "IP: $ip and PORT: NA" || echo "IP: $ip and PORT: $port"
IP: 1.2.3.4 and PORT: 12345
IP: 1.2.3.4 and PORT: 12345
$ 
$ 
$ # This is a demonstration when there is only IP4 and no port 
$ addr="1.2.3.4"
$ a=(${addr//:/ })
$ ip="${a[0]}"
port="${a[1]}"
$ len="${#a[@]}"
$ echo "${#a[@]}"
echo "$len"
1
1
$ echo "${a[@]}"
1.2.3.4
$ echo "IP: ${a[0]} and PORT: ${a[1]}"
IP: 1.2.3.4 and PORT: 
$ echo "IP: $ip and PORT: $port"
IP: 1.2.3.4 and PORT: 
$ [ "${#a[@]}" == "2" ] && echo "IP: $ip and PORT: $port" || echo "IP: $ip and PORT: NA"
[ "$len" == "2" ] && echo "IP: $ip and PORT: $port" || echo "IP: $ip and PORT: NA"
IP: 1.2.3.4 and PORT: NA
IP: 1.2.3.4 and PORT: NA
$ [ -z "${a[1]}" ] && echo "IP: ${a[0]} and PORT: NA" || echo "IP: ${a[0]} and PORT: ${a[1]}"
[ -z "$port" ] && echo "IP: $ip and PORT: NA" || echo "IP: $ip and PORT: $port"
IP: 1.2.3.4 and PORT: NA
IP: 1.2.3.4 and PORT: NA

Notice: While this arr=(${parameter//delimiter/ }) hack is a simple and nice thing to know that works perfectly as described above, but you should know it's also prone to shell word splitting if your string contains spaces and also prone to shell globbing if your string contains globbing characters e.g * ... So it wouldn't be advised then.

DenisZ avatar
cn flag
Thanks @Raffa I like this solution the best, as it seems the shortest. Yet I need to use $ip and $Port as variables for later in script, and if port is not present it should be empty so I can test with `if [ -z $port ]; then....fi`. Technically I could use 'if [ -z "${a[1]}" ]; ...' I presume.
Raffa avatar
jp flag
@DenisZ Sure ... I updated the answer for that.
DenisZ avatar
cn flag
Wonderful @Raffa, you give a full guide
Score:6
hr flag

Here's an alternative approach using bash's built-in read command:

IFS=: read IP PORT <<<"$ADDR"

Ex. (you should avoid ALL-CAPS names for your own shell variables - they are informally reserved):

$ addr=1.2.3.4:12345
$ IFS=: read ip port <<<"$addr"
$ printf 'IP is %s and PORT is %s\n' "$ip" "${port:-EMPTY}"
IP is 1.2.3.4 and PORT is 12345

This method will also handle the case where the :PORT is missing:

$ addr=1.2.3.4
$ IFS=: read ip port <<<"$addr"
$ printf 'IP is %s and PORT is %s\n' "$ip" "${port:-EMPTY}"
IP is 1.2.3.4 and PORT is EMPTY
Score:4
cn flag

If you don't know the number of sub-strings or if there many, you can use arrays also.

For example to split a MAC address like b0:25:a1:2d:b1:22 into its parts, you can use:

IFS=':' read -r -a mac <<< "b0:25:a1:2d:b1:22"

The sub-strings will be stored in the array elements "${mac[0]}" through "${mac[5]}".

I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.