61

It turns out that iptables doesn't handle leading zeros too well. As $machinenumber that is used has to have a leading zero in it for other purposes, the idea is simply to create a new variable ($nozero) based on $machinenumber, where leading zeros are stripped away.

$machinenumber is a two-digit number between 01 and 24. Currently it's 09

$machinetype is 74 for now and hasn't caused any problems before.

What I have so far is:

nozero = (echo $machinenumber | sed 's/^0*//')
iptables -t nat -I POSTROUTING -s 10.($machinetype).($nozero).0/24 -j MASQUERADE

While I believe I'm on the right track, the code results in:

ERROR - Unknown string operation

16 Answers 16

97

You don't need to use sed or another external utility. Here are a couple of ways Bash can strip the leading zeros for you.

iptables -t nat -I POSTROUTING -s "10.$machinetype.$((10#$machinenumber)).0/24" -j MASQUERADE

The $(()) sets up an arithmetic context and the 10# converts the number from base 10 to base 10 causing any leading zeros to be dropped.

shopt -s extglob
iptables -t nat -I POSTROUTING -s "10.$machinetype.${machinenumber##+(0)}.0/24" -j MASQUERADE

When extglob is turned on, the parameter expansion shown removes all leading zeros. Unfortunately, if the original value is 0, the result is a null string.

3
  • 22
    The 10# is the simplest and most elegant solution I've found so far. Thank you!
    – desgua
    Commented Dec 5, 2014 at 17:00
  • 2
    echo $((10#${machinenumber})) works in kornshell too! I was looking for a non-bashism and this should work fine. ksh M-11/16/88f
    – bgStack15
    Commented Nov 6, 2015 at 13:33
  • 1
    The accepted answer is wrong. This should be the accepted answer. $((10#$var)) is a new discovery for me. I am surprised that $((10#var)) doesn't work - I thought $ was optional inside ((...)) for variable expansion. Commented Dec 19, 2017 at 5:08
27

No, you make all (alomost all) correct. You just must:

  • remove spaces around =
  • use $() or backticks instead of ()

That would be correct:

 nozero=$(echo $machinenumber | sed 's/^0*//')

Also you must use variables without () around them. You can add "" if you want:

iptables -t nat -I POSTROUTING -s "10.$machinetype.$nozero.0/24" -j MASQUERADE

And of course variables here are not necessary. You can say simply:

iptables -t nat -I POSTROUTING -s "10.$(echo $machinenumber | sed 's/^0*//').$nozero.0/24" -j MASQUERADE
2
  • It works, cheers. What if i wanted to bake it into the iptables command, tho? I prefer not to declare any new variables.
    – Jarmund
    Commented Jun 20, 2012 at 16:40
  • Could you provide me with the correct syntax? My bash scripting is dodgy at best.
    – Jarmund
    Commented Jun 20, 2012 at 16:46
26

you can also do

machinenumber=$(expr $machinenumber + 0)
1
  • This turned out to be the simplest, working approach: iptables -t nat -I POSTROUTING -s 10.($machinetype).($machinenumber + 0).0/24 -j MASQUERADE
    – Jarmund
    Commented Sep 10, 2014 at 15:35
10

I can't comment as I don't have sufficient reputation, but I would suggest you accept Dennis's answer (which is really quite neat)

Firstly, I don't think that your answer is valid bash. In my install I get:

> machinetype=74
> machinenumber=05
> iptables -t nat -I POSTROUTING -s 10.($machinetype).($machinenumber + 0).0/24 -j MASQUERADE
-bash: syntax error near unexpected token `('
> echo 10.($machinetype).($machinenumber + 0).0/24
-bash: syntax error near unexpected token `('

If I quote it I get:

> echo "($machinetype).($machinenumber + 0)"
(74).(05 + 0)

I'm assuming you mean:

> echo 10.$(($machinetype)).$(($machinenumber + 0)).0/24
10.74.5.0/24

But, of course it's still a bad solution because of octal:

> machinenumber=09
> echo 10.$(($machinetype)).$(($machinenumber + 0)).0/24
-bash: 09: value too great for base (error token is "09")

I assume that your numbers aren't 08 or 09 at the moment.

Here's Dennis's:

> echo $((10#09))
9
> echo $((10#00))
0
> echo $((10#00005))
5
> echo $((10#))
0

Admittedly, that last one might be an input validation problem for someone.

The sed solution has the problem of:

> echo "0" | sed 's/^0*//'

>
8
nozero=$(echo $machinenumber | sed 's/^0*//')

Try without the spaces around = and with an additional $ sign.

6

A pure bash solution:

> N=0001023450 
> [[ $N =~ "0*(.*)" ]] && N=${BASH_REMATCH[1]}
> echo $N 
1023450
2
  • It would be [[ "$N" =~ ^0*(.+)$ ]] && N="${BASH_REMATCH[1]}" - that also works for N="000" and N="0". Consider also [[ "$N" =~ ^0*([0-9]+)$ ]]
    – Zrin
    Commented Feb 14, 2017 at 22:51
  • This works even if one needs to strip leading zero from something like a date: 03.20. (mm.yy.) is converted to 3.20.
    – Pila
    Commented Mar 20, 2020 at 15:41
6

Add the number with 0, it will remove leading zeroes

eg: expr 00010 + 0 #becomes 10

OR

num=00076
num=${num##+(0)}
echo $num
1
  • 1
    The second will work if extended globs are enabled.
    – leo
    Commented May 11, 2023 at 23:58
5

Using sed:

echo 000498 | sed "s/^0*\([1-9]\)/\1/;s/^0*$/0/"
498
echo 000 | sed "s/^0*\([1-9]\)/\1/;s/^0*$/0/"
0
4

I do it by using

awk '{print $1 + 0}'

I like this better than the sed approach as it still works with numbers like 0, 000, and 001.

So in your example I would replace

nozero=$(echo $machinenumber | sed 's/^0*//')

with

nozero=$(echo $machinenumber | awk '{print $1 + 0}' )

4

If you are using bash, this looks like the simplest:

nozero=$(bc<<<$machinenumber)
2
  • tested as the only working version on my Mac bash-3.2
    – ZNZNZ
    Commented Apr 22, 2019 at 9:30
  • simple and works good
    – Alwin Jose
    Commented Jan 12, 2023 at 4:03
2

I also can't comment or vote up yet, but the Duncan Irvine answer is the best.

I'd like to add a note about portability. The $((10#0009)) syntax is not portable. It works in bash and ksh, but not in dash:

$ echo $((10#09))
dash: 1: arithmetic expression: expecting EOF: "10#09"

$ dpkg -s dash | grep -i version
Version: 0.5.7-2ubuntu2

If portability is important to you, use the sed answer.

1

I would say you are very close. I do not see a requirement stated for bash, but your nonzero logic is flawed.

nonzero=`echo $machinenumber + 0 | bc`
iptables -t nat -I POSTROUTING -s 10.$machinetype.$nozero.0/24 -j MASQUERADE

Adding 0 is a common method for changing a string number into a non-padded integer. bc is a basic calculator. I use this method for removing space and zero padding from numbers all the time.

While I am not expert in iptables syntax, I am pretty sure the parenthesis are not necessary. Since I already have non-word characters bordering both variables, I do not need special enclosures around them. Word characters are;

[a-zA-z0-9_]

Using this solution, you do not lose zero as a potential value, and should be portable across all shells.

1

I don't have the reputation to respond, but @cullen-fluffy-jennings's response (| awk '{print $1 + 0}') is the best.

Any proper response should be:

  • Short
  • Standard cross-platform
  • Easy to remember
  • Work with 001, 000,and -001 without mistaking them for octal, blank, or throwing an error
  • Work through a pipe, so it can be tacked onto other conversions without extra steps, while still being able to be used inline on preexisting variables via "echo | ".

awk '{print $1 + 0}' meets all of these goals. Others do not. sed messes up on "000". $((xx + 0)) requires predefining x, and fails on values over 8. $((10#$x)) requires predefining x and fails on negative numbers. Extglob fails on zero and requires predefinition. Etc.

Everyone has awk. It's short. It's memorable. It works in all conditions. It works inline.

It's the right answer.

1

Just using parameter expansion operators, in a bash script, this will remove the leading zeros from the variable a:

a=003750
echo "${a#"${a%%[!0]*}"}"

Going from the outside in:

  • "${a#"${a%%[!0]*}"}" removes the match to the pattern "${a%%[!0]*}" from the beginning of the value of a (yes, patterns can be expansions).
  • "${a%%[!0]*}" removes the match to the pattern [!0]* from the end of the value of a. Because the operator % appears twice, it'll be greedy and so it'll remove the largest match to that pattern.
  • [!0]* will match the pattern [!0] followed by anything.
  • [!0] matches the characters indicated by !0
  • !0 this is not zero. So "${a%%[!0]*}" expands to the leading zeros of the value of a.

This will not work on a CLI interactive session. If you want to use it like that, escape the !:

a=003750
echo "${a#"${a%%[\\!0]*}"}"

If the parameter is 0 itself, the resulting expansion will be the null string.

0

I had to revisit this code the other day due to some unrelated stuff, and due to compatibility with some other software that reads the same script, i found it a lot easiest to rewrite it into this, which should still be valid bash:

iptables -t nat -I POSTROUTING -s 10.($machinetype).($machinenumber + 0).0/24 -j MASQUERADE

Basically, adding 0 forces it to be interpreted as an integer, hence automatically stripping the leading zeros

1
  • 3
    Huh? ($machinenumber + 0) isn't math syntax in bash. Commented Aug 30, 2017 at 15:32
0

If you don't have sed or awk or perl then you could use cut and grep

mn="$machinenumber"
while echo "$mn"|grep -q '^0';do mn=$(echo "$mn"|cut -c2-);done
iptables -t nat -I POSTROUTING -s 10.($machinetype).($mn).0/24 -j MASQUERADE

Works with both bash and dash shells.

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