atoi() like function in bash? - bash

Imagine that I use a state file to store a number, I read the number like this:
COUNT=$(< /tmp/state_file)
But since the file could be disrupted, $COUNT may not contain a "number", but any characters.
Other than using regex, i.e if [[ $COUNT ~ ^[0-9]+$ ]]; then blabla; fi, is there a "atoi" function that convert it to a number(0 if invalid)?
EDIT
Finally I decided to use something like this:
let a=$(($a+0))
Or
declare -i a; a="abcd123"; echo $a # got 0
Thanks to J20 for the hint.

You don't need an atoi equivalent, Bash variables are untyped. Trying to use variables set to random characters in arithmetic will just silently ignore them. eg
foo1=1
foo2=bar
let foo3=foo1+foo2
echo $foo3
Gives the result 1.
See this reference

echo $COUNT | bc should be able to cast a number, prone to error as per jurgemaister's comments...
echo ${COUNT/[a-Z]*} | bc which is similar to your regex method but not prone to error.

case "$c" in
[0-9])...
You should eat the input string charwise.

Related

Process contents in array based on type in shellscript

I have an array that has three types of data in it, integer, integer/integer, and the string value.
I have shown a sample below.
myarr = (2301/2320,Team Lifeline, 2311, 7650/7670, 232)
I have the following algorithm that I want to come up with.
For index in myarr
if index contains data as number1/number2; then
create an array, "mynumbers" to hold all the numbers starting from number1 to number2
else if index is a string
add it in "mystrarr"
else
add it in "myintarr"
done
For the first case, if I have an enter in the myarr as 2301/2320,
then the mynumbers as shown in the pseudocode will have entries from {2301, 2302, ... , 2320}. I am not able to understand on how to parse the entry in myarr and identify that it has a / in the array.
For the second situation, I am also not sure on how to identify if the entry in the myarr and know it is a string. mystrarr should have {Team Lifeline}.
For the final case, the myintarr should have {2311, 232}.
Any help would be appreciated. I am very new to shell script.
Stack Overflow is not a coding service.... but I was bored so here you go...
#!/bin/bash
myarr=(2301/2320 'Team Lifeline' 2311 7650/7670 232)
for element in "${myarr[#]}"; do
if [[ $element =~ ^[0-9]+/[0-9]+$ ]]; then
range="{${element%/*}..${element##*/}}"
mynumbers=( $(eval "echo $range") )
elif [ $element -eq $element ] 2>> /dev/null; then
intarr+=( $element )
else
strarr+=( "$element" )
fi
done
echo "mynumbers = ${mynumbers[*]}"
echo "intarr = ${intarr[*]}"
echo "strarr = ${strarr[*]}"
A lot to unpack here for inexperienced. So ask questions where I didn't cover anything. Things to note:
All assignments there are no spaces around =.
Array assignments are of the format ( element1 element2 ... )
Appending to arrays with +=(...) format
Looping through array elements for element in "${myarr[#]}"
Note that the array generated by 7650/7670 will overwrite the array generated by 2301/2320. I assume you have some kind of plan for this array, so I didn't do anything to stop it from being overwritten.
More details
This line is validating the format for 111/222:
if [[ $element =~ ^[0-9]+/[0-9]+$ ]]; then
[[ x =~ x ]] performs a regex comparison and this regex essentially just means:
^ - beginning of the string
[0-9]+ - Atleast 1 number
/ - character literal
$ - end of string
These lines are expanding your beginning and ending numbers:
range="{${element%/*}..${element##*/}}"
mynumbers=( $(eval "echo $range") )
This is maybe more complicated than it needs to be as most people try to avoid eval in general for security reasons. I'm leveraging bash's brace expansion. If you run echo {5..9}, it will output 5 6 7 8 9. This does not trigger with variables, so I cheated and used eval.
This line is checking if we are dealing with an integer:
[ $element -eq $element ] 2>> /dev/null
This works by running an integer -eq (equals) comparison on the variable against itself. This will actually fail and throw an error message on anything but an integer. This is not the way it was designed to be used which is why we discard all the error messages (2>> /dev/null).
This is a nice succinct script, but is using some unconventional practices. A longer more verbose version may be better for a beginner.
You can use regular expressions to match elements that are nothing but digits, or digits/digits, and assume everything else is a string:
#!/bin/bash
myarr=(2301/2320 "Time Lifeline" 2311 7650/7670 232)
declare -a mynumbers mystrarr myintarr
for elem in "${myarr[#]}"; do
if [[ $elem =~ ^([0-9]+)/([0-9]+)$ ]]; then
mynumbers+=($(seq ${BASH_REMATCH[1]} ${BASH_REMATCH[2]}))
elif [[ $elem =~ ^[0-9]+$ ]]; then
myintarr+=($elem)
else
mystrarr+=("$elem")
fi
done
echo mynumbers is "${mynumbers[#]}"
echo myintarr is "${myintarr[#]}"
echo mystrarr is "${mystrarr[*]}"
Jason explained a lot in his (very similar; there's only so many obvious ways to do this) answer, so to expand on where ours are different:
We both use regular expressions to match the integer/integer case, but he then goes on to extract the two numbers using parameter expansion with pattern removal options, while mine captures the two integers in the regular expression, and uses the BASH_REMATCH array to access their values as well as the seq command to generate the numbers between the two.

How do I recursively replace part of a string with another given string in bash?

I need to write bash script that converts a string of only integers "intString" to :id. intString always exists after /, may never contain any other types (create_step2 is not a valid intString), and may end at either a second / or end of line. intString may be any 1-8 characters. Script needs to be repeated for every line in a given file.
For example:
/sample/123456/url should be converted to /sample/:id/url
and /sample_url/9 should be converted to /sampleurl/:id however /sample_url_2/ should remain the same.
Any help would be appreciated!
It seems like the long way around the problem to go recursive but then I don't know what problem you are solving. It seems like a good sed command like
sed -E 's/\/[0-9]{1,}/\/:id/g'
could do it in one shot, but if you insist on being recursive then it might go something like this ...
#!/bin/bash
function restring()
{
s="$1"
s="$(echo $s | sed -E 's/\/[0-9]{1,}/\/:id/')"
if ( echo $s | grep -E '\/[0-9]{1,}' > /dev/null ) ; then
restring $s
else
echo $s
exit
fi
echo $s
}
restring "$1"
now run it
$ ./restring.sh "/foo/123/bar/456/baz/45435/andstuff"
/foo/:id/bar/:id/baz/:id/andstuff

Echo $variable$counter in a "for" loop BASH

n=1
test=1000
test1=aaa
I'm trying:
echo $test$n
to get
aaa
But I get
10001
I'm trying to use it that way because I have variables: lignePortTCP1,lignePortTCP2,lignePortTCP1, ETC in a for loop like this:
declare -i cpt3
cpt3=0
for ((i = 1; i <= cpt; i++)); do
cpt3=cpt3+1
echo "Port/Protocole : $lignePortTCP$cpt3 - Nom du Service : $ligneServiceTCP$cpt3"
done
Given the assigned variables
n=1
test1=aaa
...and you want to print aaa given the values of test and n, then put the name you want to expand in its own variable, and expand that with the ! operator, like so:
varname="test$n"
echo "${!varname}"
This is explicitly discussed in BashFAQ #6.
That said, variable indirection is not a particularly good practice -- usually, you can do better using arrays, whether associative or otherwise.
For instance:
test=( aaa bbb ccc )
n=0
echo "${test[n]}"
...for values not starting at 0:
test=( [1]=aaa [2]=bbb [3]=ccc )
n=1
echo "${test[n]}"
If you want to subtract the values of test and n, wrap the computation in $(( ... )) and use
the - operator:
$((test-n))
You could also use eval, though it's probably not better than the technique provided by Charles Duffy.
$ n=1
$ test=1000
$ test1=aaa
$ eval echo \${test$n}
aaa
One way would be to use ${!}, but you have to store the combined name in its own variable for that to work:
var=test$n
echo "${!var}"
If you have control over how the variables get assigned in the first place, it would be better to use an array. Instead of lignePortTCP1, lignePortTCP2, etc., you would assign the values to lignePortTCP[0], lignePortTCP[1], etc. and then retrieve them with ${lignePort[$n]}.

Unable to set second to last command line argument to variable

Regardless of the number of arguments passed to my script, I would like for the second to the last argument to always represent a specific variable in my code.
Executing the program I'd type something like this:
sh myprogram.sh -a arg_a -b arg_b special specific
test=("${3}")
echo $test
The results will show 'special'. So using that same idea if I try this (since I won't know that number of arguments):
secondToLastArg=$(($#-1))
echo $secondToLastArg
The results will show '3'. How do I dynamically assign the second to last argument?
You need a bit of math to get the number you want ($(($#-1))), then use indirection (${!n}) to get the actual argument.
$ set -- a b c
$ echo $#
a b c
$ n=$(($#-1))
$ echo $n
2
$ echo ${!n}
b
$
Indirection (${!n}) tells bash to use the value of n as the name of the variable to use ($2, in this case).
You can use $# as array & array chopping methods:
echo ${#:$(($#-1)):1}
It means, use 1 element starting from $(($#-1))...
If some old versions of shells do not support ${array:start:length} syntax but support only ${array:start} syntax, use below hack:
echo ${#:$(($#-1))} | { read x y ; echo $x; } # OR
read x unused <<< `echo ${#:$(($#-1))}`

Assigning dynamic value to variable

how can I assign a dynamic value to variable? The simplest method I know about is by using a function. For example
fn(){
VAR=$VAL
}
VAL=value
fn
echo $VAR
will output
value
but I want something simpler, like
VAR=$VAL
VAL=value
echo $VAR
to output
value
What command should I use? Preferably to be compatible with dash.
Thanks!
UPDATE: Removed #!/bin/sh in connection to dash. Thank "Ignacio Vazquez-Abrams" for the explanation!
UPDATE 2: Adding the source for my script to better understand the situation.
INPUT=`dpkg -l|grep ^rc|cut -d' ' -f3`
filter(){
echo *$A*
}
for A in $INPUT;do find ~ -iname `filter`|grep ^$HOME/\\.|grep -iz --color $A;done
This script should help finding the remaining configuration files of removed packages.
Okay, if function is not good, then maybe calling eval is okay?
export VAR='echo $VAL'
VAL=10
eval $VAR
This will display
10
How about a simple function that sets value?
# export is needed so f() can use it.
export VAR
f() {
VAR=$#
}
f 10
echo $VAR
f 20
echo $VAR
The code above will display:
10
20
If I understand your needs, you want an indirection, so try the following shell code (tested with dash) :
var=foo
x=var
eval $x=another_value
echo $var
output :
another_value
finally:
Each times you need to modify var, you need to run eval $x=foobar

Resources