Delete positional parameters in Bash? - bash

You can skip positional parameters with shift but can you delete positional parameters by passing the position?
x(){ CODE; echo "$#"; }; x 1 2 3 4 5 6 7 8
> 1 2 4 5 6 7 8
I would like to add CODE to x() to delete positional parameter 3. I don't want to do echo "${#:1:2} ${#:4:8}". After running CODE, $# should only contain "1 2 4 5 6 7 8".

The best way, if you want to be able to pass on the parameters to another process, or handle space separated parameters, is to re-set the parameters:
$ x(){ echo "Parameter count before: $#"; set -- "${#:1:2}" "${#:4:8}"; echo "$#"; echo "Parameter count after: $#"; }
$ x 1 2 3 4 5 6 7 8
Parameter count before: 8
1 2 4 5 6 7 8
Parameter count after: 7
To test that it works with non-trivial parameters:
$ x $'a\n1' $'b\b2' 'c 3' 'd 4' 'e 5' 'f 6' 'g 7' $'h\t8'
Parameter count before: 8
a
1 2 d 4 e 5 f 6 g 7 h 8
Parameter count after: 7
(Yes, $'\b' is a backspace)

x(){
#CODE
params=( $* )
unset params[2]
set -- "${params[#]}"
echo "$#"
}
Input:
x 1 2 3 4 5 6 7 8
Output:
1 2 4 5 6 7 8

From tldp
# The "unset" command deletes elements of an array, or entire array.
unset colors[1] # Remove 2nd element of array.
# Same effect as colors[1]=
echo ${colors[#]} # List array again, missing 2nd element.
unset colors # Delete entire array.
# unset colors[*] and
#+ unset colors[#] also work.
echo; echo -n "Colors gone."
echo ${colors[#]} # List array again, now empty.

You can call set and reset the positional paramaters at any time
for example
function q {
echo ${#}
set $2 $3 $4
echo ${#}
set $4
echo ${#}
}
q 1 2 3 4
then slice out what you dont want from the array, the below code does that... not sure if its the best way to do it though, was on stack looking for a better way ; )
#!/bin/bash
q=( one two three four five )
echo -e "
(remove) { [:range:] } <- [:list:]
| [:range:] => return list with range removed range is in the form of [:digit:]-[:digit:]
"
function remove {
if [[ $1 =~ ([[:digit:]])(-([[:digit:]]))? ]]; then
from=${BASH_REMATCH[1]}
to=${BASH_REMATCH[3]}
else
echo bad range
fi;shift
array=( ${#} )
local start=${array[#]::${from}}
local rest
[ -n "$to" ] && rest=${array[#]:((${to}+1))} || rest=${array[#]:((${from}+1))}
echo ${start[#]} ${rest[#]}
}
q=( `remove 1 ${q[*]}` )
echo ${q[#]}

while loop over "$#" with shift + set: move each parameter from first to last position, except "test"
# remove option "test" from positional parameters
i=1
while [ $i -le $# ]
do
var="$1"
case "$var" in
test)
echo "param \"$var\" deleted"
i=$(($i-1))
;;
*)
set -- "$#" "$var"
;;
esac
shift
i=$(($i+1))
done

Related

How to pass multiple arrays to function in shell script and how to access it in the function

I am new in shell script,
I want to pass two string arrays to function as an argument and want to access that in the function for further operation.
Please help me in that.
Thank you.
When you pass one or array to the function, it actually passes each element as an argument. You can retrieve them all from $#
If you know the size of each array, you can split them by the size. Otherwise, you need some tricky methods to split the arrays. For example, pass another parameter which will never present in these arrays.
Here is an example.
#!/bin/bash
func()
{
echo "all params: $#"
echo "para size: ${##}"
p1=()
p2=()
i=1
for e in "${#}" ; do
if [[ "$e" == "|" ]] ; then
i=$(($i+1))
continue
fi
if [ $i -eq 1 ] ; then
p1+=($e)
else
p2+=($e)
fi
done
echo "p1: ${p1[#]}"
echo "p2: ${p2[#]}"
}
a=(1 2 3 4 5)
b=('a' 'b' 'c')
func "${a[#]}" "|" "${b[#]}"
This is the output of the script.
$ ./test.sh
all params: 1 2 3 4 5 | a b c
para size: 9
p1: 1 2 3 4 5
p2: a b c
Bash isn't great at handling array passing to functions, but it can be done. What you will need to pass is 4 parameters: number-of-elements-array1 array1 number-of-elements-array2 array2. For example, you can do:
#!/bin/bash
myfunc() {
local nelem1=$1 ## set no. elements in array 1
local -a array1
local nelem2
local -a array2
shift ## skip to next arg
while ((nelem1-- != 0)); do ## loop nelem1 times
array1+=("$1") ## add arg to array1
shift ## skip to next arg
done
nelem2=$1 ## set no. elements in array 2
shift ## ditto for array 2
while ((nelem2-- != 0)); do
array2+=("$1")
shift
done
declare -p array1 ## output array contents
declare -p array2
}
a1=("My dog" "has fleas")
a2=("my cat" has none)
myfunc ${#a1[#]} "${a1[#]}" ${#a2[#]} "${a2[#]}"
Example Use/Output
$ bash scr/tmp/stack/function-multi-array.sh
declare -a array1=([0]="My dog" [1]="has fleas")
declare -a array2=([0]="my cat" [1]="has" [2]="none")

Passing and returning args in bash function [duplicate]

Looking the "Array" section in the bash(1) man page, I didn't find a way to slice an array.
So I came up with this overly complicated function:
#!/bin/bash
# #brief: slice a bash array
# #arg1: output-name
# #arg2: input-name
# #args: seq args
# ----------------------------------------------
function slice() {
local output=$1
local input=$2
shift 2
local indexes=$(seq $*)
local -i i
local tmp=$(for i in $indexes
do echo "$(eval echo \"\${$input[$i]}\")"
done)
local IFS=$'\n'
eval $output="( \$tmp )"
}
Used like this:
$ A=( foo bar "a b c" 42 )
$ slice B A 1 2
$ echo "${B[0]}" # bar
$ echo "${B[1]}" # a b c
Is there a better way to do this?
See the Parameter Expansion section in the Bash man page. A[#] returns the contents of the array, :1:2 takes a slice of length 2, starting at index 1.
A=( foo bar "a b c" 42 )
B=("${A[#]:1:2}")
C=("${A[#]:1}") # slice to the end of the array
echo "${B[#]}" # bar a b c
echo "${B[1]}" # a b c
echo "${C[#]}" # bar a b c 42
echo "${C[#]: -2:2}" # a b c 42 # The space before the - is necesssary
Note that the fact that a b c is one array element (and that it contains an extra space) is preserved.
There is also a convenient shortcut to get all elements of the array starting with specified index. For example "${A[#]:1}" would be the "tail" of the array, that is the array without its first element.
version=4.7.1
A=( ${version//\./ } )
echo "${A[#]}" # 4 7 1
B=( "${A[#]:1}" )
echo "${B[#]}" # 7 1
Array slicing like in Python (From the rebash library):
array_slice() {
local __doc__='
Returns a slice of an array (similar to Python).
From the Python documentation:
One way to remember how slices work is to think of the indices as pointing
between elements, with the left edge of the first character numbered 0.
Then the right edge of the last element of an array of length n has
index n, for example:
```
+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
```
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1:-2 "${a[#]}")
1 2 3
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0:1 "${a[#]}")
0
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 1:1 "${a[#]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 2:1 "${a[#]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice -2:-3 "${a[#]}")" ] && echo empty
empty
>>> [ -z "$(array.slice -2:-2 "${a[#]}")" ] && echo empty
empty
Slice indices have useful defaults; an omitted first index defaults to
zero, an omitted second index defaults to the size of the string being
sliced.
>>> local a=(0 1 2 3 4 5)
>>> # from the beginning to position 2 (excluded)
>>> echo $(array.slice 0:2 "${a[#]}")
>>> echo $(array.slice :2 "${a[#]}")
0 1
0 1
>>> local a=(0 1 2 3 4 5)
>>> # from position 3 (included) to the end
>>> echo $(array.slice 3:"${#a[#]}" "${a[#]}")
>>> echo $(array.slice 3: "${a[#]}")
3 4 5
3 4 5
>>> local a=(0 1 2 3 4 5)
>>> # from the second-last (included) to the end
>>> echo $(array.slice -2:"${#a[#]}" "${a[#]}")
>>> echo $(array.slice -2: "${a[#]}")
4 5
4 5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -4:-2 "${a[#]}")
2 3
If no range is given, it works like normal array indices.
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -1 "${a[#]}")
5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -2 "${a[#]}")
4
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0 "${a[#]}")
0
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1 "${a[#]}")
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice 6 "${a[#]}"; echo $?
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice -7 "${a[#]}"; echo $?
1
'
local start end array_length length
if [[ $1 == *:* ]]; then
IFS=":"; read -r start end <<<"$1"
shift
array_length="$#"
# defaults
[ -z "$end" ] && end=$array_length
[ -z "$start" ] && start=0
(( start < 0 )) && let "start=(( array_length + start ))"
(( end < 0 )) && let "end=(( array_length + end ))"
else
start="$1"
shift
array_length="$#"
(( start < 0 )) && let "start=(( array_length + start ))"
let "end=(( start + 1 ))"
fi
let "length=(( end - start ))"
(( start < 0 )) && return 1
# check bounds
(( length < 0 )) && return 1
(( start < 0 )) && return 1
(( start >= array_length )) && return 1
# parameters start with $1, so add 1 to $start
let "start=(( start + 1 ))"
echo "${#: $start:$length}"
}
alias array.slice="array_slice"
At the risk of beating a dead horse, I was inspired by #jandob's answer and made this version that
Is simpler (doesn't have so much shift logic or rewriting of variables as often).
Respects quoted strings without dealing with IFS (-r mode only).
Allows the user to specify [start, end) slicing or [start, length] slicing via -l flag.
Allows you to echo the resulting array (default behavior), or "return" it into a new array for use in the calling parent (via -r slicedArray).
Note: namerefs are only supported in Bash >= 4.3. To support earlier versions of Bash (i.e. Mac without Brew's bash), you'll need to use indirection instead: use a temp var to access array parameters, e.g. declare arrValuesCmd="$1[#]"; declare arr=("${!arrValuesCmd}"), and use eval for return values, e.g. eval $retArrName='("${newArr[#]}")' (note the single quotes around the array declaration).
array.slice() {
# array.slice [-l] [-r returnArrayName] myArray 3 5
# Default functionality is to use second number as end index for slice (exclusive).
# Can instead use second number as length by passing `-l` flag.
# `echo` doesn't maintain quoted entries, so pass in `-r returnArrayName` to keep them.
declare isLength
declare retArrName
declare OPTIND=1
while getopts "lr:" opt; do
case "$opt" in
l)
# If `end` is slice length instead of end index
isLength=true
;;
r)
retArrName="$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
declare -n arr="$1"
declare start="$2"
declare end="$3"
declare arrLength="${#arr[#]}"
declare newArr=()
declare newArrLength
# Bash native slicing:
# Positive index values: ${array:start:length}
# Negative index values: ${array: start: length}
# To use negative values, a space is required between `:` and the variable
# because `${var:-3}` actually represents a default value,
# e.g. `myVar=${otherVal:-7}` represents (pseudo-code) `myVar=otherVal || myVar=7`
if [[ -z "$end" ]]; then
# If no end is specified (regardless of `-l`/length or index), default to the rest of the array
newArrLength="$arrLength"
elif [[ -n "$isLength" ]]; then
# If specifying length instead of end-index, use native bash array slicing
newArrLength="$(( end ))"
else
# If specifying end-index, use custom slicing based on a range of [start, end):
newArrLength="$(( end - start ))"
fi
newArr=("${arr[#]: start: newArrLength}")
if [[ -n "$retArrName" ]]; then
declare -n retArr="$retArrName"
retArr=("${newArr[#]}")
else
echo "${newArr[#]}"
fi
}
Examples:
myArray=(x y 'a b c' z 5 14) # length=6
array.slice myArray 2 4
# > a b c z
array.slice -l myArray 3 2
# > z 5
# Note: Output was manually quoted to show the result more clearly.
# Actual stdout content won't contain those quotes, which is
# why the `-r returnArray` option was added.
array.slice -r slicedArray myArray -5 -3 # equivalent of [2, 4)
# > (null)
echo -e "myArray (length=${#myArray[#]}): ${myArray[#]} \nslicedArray (length=${#slicedArray[#]}): ${slicedArray[#]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=2): 'a b c' z
array.slice -lr slicedArray myArray -5 3 # length instead of index, equivalent of [2, 5)
# > (null)
echo -e "myArray (length=${#myArray[#]}): ${myArray[#]} \nslicedArray (length=${#slicedArray[#]}): ${slicedArray[#]}"
# > myArray (length=6): x y 'a b c' z 5 14
# > slicedArray (length=3): 'a b c' z 5

bash check if value exist in array , it seems single(i) is trying to match with double digit numeric (10 is one of of the value in array)

it seems single(i) is trying to match with double digit numeric (10 is one of of the value in array)
STATE 1
abc#xyz $ i=1
abc#xyz $ allStatus=(2 3 4 5 6 7 8 9 10)
abc#xyz $ if [[ ! ${allStatus[#]} =~ $i ]]
> then
> echo OK
> fi
STATE 2
abc#xyz $ i=2
abc#xyz $ allStatus=(1 3 4 5 6 7 8 9 10)
abc#xyz $
abc#xyz $ if [[ ! ${allStatus[#]} =~ $i ]]
> then
> echo OK
> fi
OK
abc#xyz $
all i want is, STATE 1 should echo/print OK
That's because the value after =~ is interpreted as a regular expression, and the string 10 matches the regular expression 1.
You need to check that there's a space or string start before the value and space or string end after the value:
[[ ${arr[#]} =~ ( |^)$i( |$) ]]
This can still fail if the value a 1 b belongs to the array:
foStatus=(2 3 'a 1 b')
The correct way is to iterate over the values and check for equality:
arr=(2 3 1 4 9 10 'a 1 b')
i=1
for v in "${arr[#]}" ; do
if [[ $v == "$i" ]] ; then
echo OK
fi
done
Since you are checking a numeric status exist, you can use a sparse array index and do:
#!/usr/bin/env bash
i=2
allStatus=([1]=1 [3]=1 [4]=1 [5]=1 [6]=1 [7]=1 [8]=1 [9]=1 [10]=1)
if ((allStatus[i])); then
echo OK
fi
You can also create a bit-field and check bit is on:
#!/usr/bin/env bash
i=2
allStatus=(1 3 4 5 6 7 8 9 10)
# Set all bits corresponding to status by expanding array elements
# into the arithmetic expression:
# 0 | 1 << element1 | 1 << element2 |...
allStatusBin=$((0${allStatus[#]/#/|1<<}))
# Or set it directly in binary
#allStatusBin=$((2#11111111010))
if ((1<<i&allStatusBin)); then
echo OK
fi

Print all numbers between two given numbers

I'm working on a Bash script which that takes in two integers and outputs all the numbers in between the two. It would look something like this:
Input:
bash testScript 3 10
3
4
5
6
7
8
9
10
This is some code that I wrote that I thought would work but I haven't had much luck getting it to work yet.
read myvar
read myvar2
while [ $myvar -le myvar2 ]
do
echo $myvar
myvar=$(($myvar+1))
//timer in-between numbers
sleep .5
done
Bash supports c style for loops using a double parenthesis construct:
$ for ((x=3; x<=10; x++)); { echo $x; }
3
4
5
6
7
8
9
10
Or, brace expansion:
$ for i in {3..6}; do echo $i; done
3
4
5
6
Problem with brace expansion is you need to use eval to use variables...
A common GNU utility for this is seq but it is not POSIX, so may not be on every *nix. If you want to write a similar function in Bash, it would look like this:
my_seq ()
# function similar to seq but with integers
# my_seq [first [incr]] last
{
incr=1
start=1
if [[ $# -gt 2 ]]; then
start=$1
incr=$2
end=$3
elif [[ $# -gt 1 ]]; then
start=$1
end=$2
else
end=$1
fi
for ((x=start; x<=end; x+=incr)); { echo $x; }
}
Then call that with 1, 2 or 3 arguments:
$ my_seq 30 10 60
30
40
50
60
with brace expansion
$ echo {3..10} | tr ' ' '\n'
or for variables with eval
$ a=3; b=10; eval echo {$a..$b} | ...
or, if you have awk
$ awk -v s=3 -v e=10 'BEGIN{while(s<=e) print s++}'
Use positional parameters:
myvar=$1
myvar2=$2
while [ $myvar -le $myvar2 ]
do
echo $myvar
myvar=$(($myvar+1))
#timer in-between numbers
sleep .5
done
Output:
scottsmudger#ns207588:~ $ bash test 1 5
1
2
3
4
5
See http://www.tldp.org/LDP/abs/html/othertypesv.html
Your posted codes are not aligned properly, not sure if it's your actual codes. But only problem other than the alignment is you missed a $ for myvar2 in the while statment.
read myvar
read myvar2
while [ $myvar -le $myvar2 ]
do
echo $myvar
myvar=$(($myvar+1))
#//timer in-between numbers
sleep .5
done

How to remove nth element from command arguments in bash

How to I remove the nth element from an argument list in bash?
Shift only appears to remove the first n, but I want to keep some of the first. I want something like:
#!/bin/sh
set -x
echo $#
shift (from position 2)
echo $#
So when I call it - it removes "house" from the list:
my.sh 1 house 3
1 house 3
1 3
Use the set builtin and shell parameter expansion:
set -- "${#:1:1}" "${#:3}"
would remove the second positonal argument.
You could make it generic by using a variable:
n=2 # This variable denotes the nth argument to be removed
set -- "${#:1:n-1}" "${#:n+1}"
a=$1
shift; shift
echo $a $#
If someone knows how to do this in a better way I am all ears!
If you want to use bash you can use a bash array
#!/bin/bash
arg=($0 $#)
or if you don't need argument $0
arg=($#)
# argument to remove
rm_arg=2
arg=(${arg[#]:0:$rm_arg} ${arg[#]:$(($rm_arg + 1))})
echo ${arg[#]}
Now instead of referencing $1 you might use ${arg[1]} .
Remember that bash arguments has an argument $0 and bash arrays have an element [0]. So, if you don't assign bash argument $0 to the first element of a bash array [0] , Then you will find bash argument $1 in your bash array [0] .
arg=($#)
for VAR in $#; do
case $VAR in
A)
echo removing $VAR
arg=($(echo $#| sed "s/$VAR//"))
;;
*)
echo ${arg[#]}
;;
esac
done
Results:
./test.sh 1 2 3 A 4 5
1 2 3 A 4 5
1 2 3 A 4 5
1 2 3 A 4 5
removing A
1 2 3 4 5
1 2 3 4 5

Resources