Bash, argument list segment - bash

If you have a list in python, and you want the elements from 2 to n can do something nice like
list[2:]
I'd like to something similar with argv in Bash. I want to pass all the elements from $2 to argc to a command. I currently have
command $2 $3 $4 $5 $6 $7 $8 $9
but this is less than elegant. Would would be the "proper" way?

you can do "slicing" as well, $# gets all the arguments in bash.
echo "${#:2}"
gets 2nd argument onwards
eg
$ cat shell.sh
#!/bin/bash
echo "${#:2}"
$ ./shell.sh 1 2 3 4
2 3 4

Store $1 somewhere, then shift and use $#?

script1.sh:
#!/bin/bash
echo "$#"
script2.sh:
#!/bin/bash
shift
echo "$#"
$ sh script1.sh 1 2 3 4
1 2 3 4
$ sh script2.sh 1 2 3 4
2 3 4

Related

how to group all arguments as position argument for `xargs`

I have a script which takes in only one positional parameter which is a list of values, and I'm trying to get the parameter from stdin with xargs.
However by default, xargs passes all the lists to my script as positional parameters, e.g. when doing:
echo 1 2 3 | xargs myScript, it will essentially be myScript 1 2 3, and what I'm looking for is myScript "1 2 3". What is the best way to achieve this?
Change the delimiter.
$ echo 1 2 3 | xargs -d '\n' printf '%s\n'
1 2 3
Not all xargs implementations have -d though.
And not sure if there is an actual use case for this but you can also resort to spawning another shell instance if you have to. Like
$ echo -e '1 2\n3' | xargs sh -c 'printf '\''%s\n'\'' "$*"' sh
1 2 3
If the input can be altered, you can do this. But not sure if this is what you wanted.
echo \"1 2 3\"|xargs ./myScript
Here is the example.
$ cat myScript
#!/bin/bash
echo $1; shift
echo $1; shift
echo $1;
$ echo \"1 2 3\"|xargs ./myScript
1 2 3
$ echo 1 2 3|xargs ./myScript
1
2
3

Removing a specified number of lines from both head and tail of a stream

k=$1
m=$2
fileName=$3
head -n -$k "$fileName" | tail -n +$m
I have the bash code.
when I execute it, it only removes less than what it should remove. like ./strip.sh 4 5 hi.txt > bye.txt should remove first 4 lines and last 5 lines, but it only removes first 4 lines and last "4" lines. Also, when I execute ./strip.sh 1 1 hi.txt > bye.txt, it only removes last line, not first line....
#!/bin/sh
tail -n +"$(( $1 + 1 ))" <"$3" | head -n -"$2"
Tested as follows:
set -- 4 5 /dev/stdin # assign $1, $2 and $3
printf '%s\n' {1..20} | tail -n +"$(( $1 + 1 ))" <"$3" | head -n -"$2"
...which correctly prints numbers between 5 and 15, trimming the first 4 from the front and 5 from the back. Similarly, with set -- 3 6 /dev/stdin, numbers between 4 and 14 inclusive are printed, which is likewise correct.

Print bash arguments in reverse order

I have to write a script, which will take all arguments and print them in reverse.
I've made a solution, but find it very bad. Do you have a smarter idea?
#!/bin/sh
> tekst.txt
for i in $*
do
echo $i | cat - tekst.txt > temp && mv temp tekst.txt
done
cat tekst.txt
Could do this
for (( i=$#;i>0;i-- ));do
echo "${!i}"
done
This uses the below
c style for loop
Parameter indirect expansion
(${!i}towards the bottom of the page)
And $# which is the number of arguments to the script
you can use this one liner:
echo $# | tr ' ' '\n' | tac | tr '\n' ' '
bash:
#!/bin/bash
for i in "$#"; do
echo "$i"
done | tac
call this script like:
./reverse 1 2 3 4
it will print:
4
3
2
1
Portably and POSIXly, without arrays and working with spaces and newlines:
Reverse the positional parameters:
flag=''; c=1; for a in "$#"; do set -- "$a" ${flag-"$#"}; unset flag; done
Print them:
printf '<%s>' "$#"; echo
Reversing a simple string, by spaces
Simply:
#!/bin/sh
o=
for i;do
o="$i $o"
done
echo "$o"
will work as
./rev.sh 1 2 3 4
4 3 2 1
Or
./rev.sh world! Hello
Hello world!
If you need to output one line by argument
Just replace echo by printf "%s\n":
#!/bin/sh
o=
for i;do
o="$i $o"
done
printf "%s\n" $o
Reversing an array of strings
If your argument could contain spaces, you could use bash arrays:
#!/bin/bash
declare -a o=()
for i;do
o=("$i" "${o[#]}")
done
printf "%s\n" "${o[#]}"
Sample:
./rev.sh "Hello world" print will this
this
will
print
Hello world
As a function (If you're ok to play with eval.
But eval is evil!!
rev() { eval "set --" $(seq -f '"${%g}"' $# -1 1);printf '%s\n' "$#";}
Then
rev Hello\ world print will this
this
will
print
Hello world

How to cut and assign the string to a dynamic array inside the for loop

This is what i have done to perform this function but I am not getting what i want.
#!/bin/sh
DIRECTIONPART1=4-7-9
for (( i=1; i<=3; i++ ))
do
x=`echo $DIRECTIONPART1| awk -F'-' '{print $i}'`
myarray[$i]=$x
done
for (( c=1; c<=3; c++ ))
do
echo ${myarray[$c]}
done
Problem we realised at this step
x=`echo $DIRECTIONPART1| awk -F'-' '{print $i}'`
Please help me in getting the result
This is what i get :
4-7-9
4-7-9
4-7-9
But I want this:
4
7
9
you are right with line of problem. The problem is that you cant use $i as variable in print. I have tried little workaround which worked for me:
x=`echo $DIRECTIONPART1| awk -F '-' -v var=$i '{print $var }'`
in all it looks like:
#!/bin/sh
DIRECTIONPART1=4-7-9
for (( i=1; i<=3; i++ ))
do
x=`echo $DIRECTIONPART1| awk -F '-' -v var=$i '{print $var }'`
myarray[$i]=$x
done
for (( c=1; c<=3; c++ ))
do
echo ${myarray[$c]}
done
with expected output:
# sh test.sh
4
7
9
#
The simplest portable way to get the desired output is to use $IFS (in a subshell):
#!/bin/sh
DIRECTIONPART1=4-7-9
(IFS=- && echo $DIRECTIONPART1)
The shell array would not work portably, as POSIX, ksh, and bash do not
agree on arrays. POSIX doesn't have any; ksh and bash use different syntax.
If you really want an array, I would suggest to do the entire thing in awk:
#!/bin/sh
DIRECTIONPART1=4-7-9
awk -v v=${DIRECTIONPART1} 'BEGIN {
n=split(v,a,"-")
for (i=1;i<=n;i++) {
print a[i]
}
}'
This will produce one line for each value in the string:
4
7
9
And if you want bash arrays, drop the #!/bin/sh, and do something like this:
#!/bin/bash
DIRECTIONPART1=4-7-9
A=( $(IFS=- && echo $DIRECTIONPART1) )
for ((i=0;i<=${#A[#]};i++))
do
echo ${A[i]}
done
Calling awk multiple times, or even once, is not the right thing to do. Use the bash built-in read to populate the array.
# Note that the quotes here are only necessary to
# work around a bug that was fixed in bash 4.3. It
# doesn't hurt to use them in any version, though.
$ IFS=- read -a myarray <<< "$DIRECTIONPART_1"
$ printf '%s\n' "${myarray[#]}"
4
7
9
[akshay#localhost tmp]$ bash test.sh
#!/usr/bin/env bash
DIRECTIONPART1=4-7-9
# Create array
IFS='-' read -a array <<< "$DIRECTIONPART1"
#To access an individual element:
echo "${array[0]}"
#To iterate over the elements:
for element in "${array[#]}"
do
echo "$element"
done
#To get both the index and the value:
for index in "${!array[#]}"
do
echo "$index ${array[index]}"
done
Output
[akshay#localhost tmp]$ bash test.sh
4
4
7
9
0 4
1 7
2 9
OR
[akshay#localhost tmp]$ cat test1.sh
#!/usr/bin/env bash
DIRECTIONPART1=4-7-9
array=(${DIRECTIONPART1//-/ })
for index in "${!array[#]}"
do
echo "$index ${array[index]}"
done
Output
[akshay#localhost tmp]$ bash test1.sh
0 4
1 7
2 9

bash: passing params from $3 onwards to another program (say tar)

let's say my script takes filenames starting from $3
e.g.
archive u 2342 a.txt b.png c.html d.js
archive u 2222 a.txt b.png
I want to zip all the files listed starting from $3
How can I do that?
thanks
zip archivename.zip "${#:3}"
The bash command shift is your friend.
The script example.sh
#!/bin/bash
shift 3
echo $*
called with example.sh 1 2 3 4 5 will output 4 5.
FIRST=$1
shift
SECOND=$1
shift
echo $FIRST
echo $SECOND
echo tar cf archive.tar "$#"
Input:
./test.sh 1 2 3 4 5
Output:
1
2
tar cf archive.tar 3 4 5

Resources