Remove last argument from argument list of shell script (bash) - bash

This question concerns a bash script that is run in automator osx. I am using automator actions to get and filter a bunch of file references from the finder. Then I append to that list the name of the parent folder, also via an automator action. Automator then feeds these arguments to an action called "run shell script". I am not sure exactly how automator invokes the script but the argument list looks like this when echoed with: echo "$#"
/Volumes/G-Raid/Online/WAV_TEST/Testbok 50/01/01000 43-001.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/02/02000 43-002.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/03/03000 43-003.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50
In this case path to 3 files and a folder.
In the shell script I launch an application called ripcheckc* with the args passed from automator minus the last argument(the folder) in the list.
I use this to remove the last argument:
_args=( "$#" )
unset _args[${#_args[#]}-1]
And this is echo $_args:
/Volumes/G-Raid/Online/WAV_TEST/Testbok 50/01/01000 43-001.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/02/02000 43-002.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/03/03000 43-003.wav
Same as before but without the folder.
Now, if I run ripcheckc with "$#" as argument it works (but fails later on because of that last path in the argument list) If I use ${_args[#]} the application will just abort silently. When I echo $# and _args the output looks identical except for the last argument.
My question is - what is the difference between $# and $_args that make the first valid input and the second not?
*The application is ripcheckc
I hope my question makes sense.
EDIT: Solved.

I have used this bash one-liner before
set -- "${#:1:$(($#-1))}"
It sets the argument list to the current argument list, less the last argument.
How it works:
$# is the number of arguments
$((...)) is an arithmetic expression, so $(($#-1)) is one less than the number of arguments.
${variable:position:count} is a substring expression: it extracts count characters from variable starting at position. In the special case where variable is #, which means the argument list, it extracts count arguments from the list beginning at position. Here, position is 1 for the first argument and count is one less than the number of arguments worked out previously.
set -- arg1...argn sets the argument list to the given arguments
So the end result is that the argument list is replaced with a new list, where the new list is the original list except for the last argument.

Assuming that you already have an array, you can say:
unset "array[${#array[#]}-1]"
For example, if your script contains:
array=( "$#" )
unset "array[${#array[#]}-1]" # Removes last element -- also see: help unset
for i in "${array[#]}"; do
echo "$i"
done
invoking it with: bash scriptname foo bar baz produces:
foo
bar

You can also get all but the last argument with
"${#:0:$#}"
which, honestly, is a little sketchy, since it seems to be (ab)using the fact that arguments are numbered starting with 1, not 0.
Update: This only works due to a bug (fixed in 4.1.2 at the latest) in handling $#. It works in version 3.2.

set -- "${#:1:$#-1}"
sets the parameter list to first up to penultimate, removing the last one

Related

Arithmetic in shell script (arithmetic in string)

I'm trying to write a simple script that creates five textfiles enumerated by a variable in a loop. Can anybody tell my how to make the arithmetic expression be evaluated. This doesn't seem to work:
touch ~/test$(($i+1)).txt
(I am aware that I could evaluate the expression in a separate statement or change of the loop...)
Thanks in advance!
The correct answer would depend on the shell you're using. It looks a little like bash, but I don't want to make too many assumptions.
The command you list touch ~/test$(($i+1)).txt will correctly touch the file with whatever $i+1 is, but what it's not doing, is changing the value of $i.
What it seems to me like you want to do is:
Find the largest value of n amongst the files named testn.txt where n is a number larger than 0
Increment the number as m.
touch (or otherwise output) to a new file named testm.txt where m is the incremented number.
Using techniques listed here you could strip the parts of the filename to build the value you wanted.
Assume the following was in a file named "touchup.sh":
#!/bin/bash
# first param is the basename of the file (e.g. "~/test")
# second param is the extension of the file (e.g. ".txt")
# assume the files are named so that we can locate via $1*$2 (test*.txt)
largest=0
for candidate in (ls $1*$2); do
intermed=${candidate#$1*}
final=${intermed%%$2}
# don't want to assume that the files are in any specific order by ls
if [[ $final -gt $largest ]]; then
largest=$final
fi
done
# Now, increment and output.
largest=$(($largest+1))
touch $1$largest$2

Use default value if no arguments are set except the first one in a bash script

From "Process all arguments except the first one (in a bash script)" I have learned how to get all arguments except the first one. Is it also possible to substitute null with another value, so I can define a default value?
I've tried the following, but I think I don't get some little detail about the syntax:
DEFAULTPARAM="up"
echo "${#:2-$DEFAULTPARAM}"
Here are my test cases:
$ script.sh firstparam another more
another more
$ script.sh firstparam another
another
$ script.sh firstparam
up
You cannot combine 2 expressions like that in bash. You can get all arguments from position 2 into a separate variable and then check/get default value:
defaultParam="up"
from2nd="${#:2}" # all arguments from position 2
echo "${from2nd:-$defaultParam}" # return default value if from2nd is empty
PS: It is recommended to avoid all caps variable names in your bash script.
Testing:
./script.sh firstparam
up
./script.sh firstparam second
second
./script.sh firstparam second third
second third
bash doesn't generally allow combining different types of special parameter expansions; this is one of the combos that doesn't work. I think the easiest way to get this effect is to do an explicit test to decide whether to use the default value. Exactly how to do this depends on the larger situation, but probably something like this:
#!/bin/bash
DEFAULTPARAM="up"
if (( $# >= 2 )); then
allbutfirstarg=("${#:2}")
else
allbutfirstarg=("$DEFAULTPARAM")
fi
echo "${allbutfirstarg[#]}"

how to filter a command subsitution from the resulting value of a readlink for symlink?

This may be poorly titled as I'm not fully sure what the process is called.
Basically I want to get only the last part of a symlink path, and I'm trying to use the same method I use with PWD.
For example:
if I do
PWD
it prints
/opt/ct/mydir
if I do
echo ${PWD##*/}
it prints only the last part
mydir
So using that design I can do
readlink mysymlink
which gives
/opt/ct/somedir
and I can do
TMP=$(readlink mysymlink)
echo ${TMP##*/}
and it will print
somedir
So now how can I combine that last part into one line like
TMP=$(readlink mysymlink && echo ${TMP##*/})
???
The example I show gives me 2 concatenated results.. one with the full path and one with just the part I want. I only want that last directory.
I also tried
TMP=${ $(readlink mysymlink)##*/}
to no avail
Variable substitution suffixes can only be used with variables, not command substitutions. You either have to set the variable and modify it in separate statements, as in your first attempt, or use additional command substitutions:
TMP=$(basename $(readlink))

Open file in bash script

I've got a bash script accepting several files as input which are mixed with various script's options, for example:
bristat -p log1.log -m lo2.log log3.log -u
I created an array where i save all the index where i can find files in the script's call, so in this case it would be an arrat of 3 elements where
arr_pos[0] = 2
arr_pos[1] = 4
arr_pos[3] = 5
Later in the script I must call "head" and "grep" in those files and i tried this way
head -n 1 ${arr_pos[0]}
but i get this error non runtime
head: cannot open `2' for reading: No such file or directory
I tried various parenthesis combinations, but I can't find which one is correct.
The problem here is that ${arr_pos[0]} stores the index in which you have the file name, not the file name itself -- so you can't simply head it. The array storing your arguments is given by $#.
A possible way to access the data you want is:
#! /bin/bash
declare -a arr_pos=(2 4 5)
echo ${#:${arr_pos[0]}:1}
Output:
log1.log
The expansion ${#:${arr_pos[0]}:1} means you're taking the values ranging from the index ${arr_pos[0]} in the array $#, to the element of index ${arr_pos[0]} + 1 in the same array $#.
Another way to do so, as pointed by #flaschenpost, is to eval the index preceded by $, so that you'd be accessing the array of arguments. Although it works very well, it may be risky depending on who is going to run your script -- as they may add commands in the argument line.
Anyway, you may should try to loop through the entire array of arguments by the beginning of the script, hashing the values you find, so that you won't be in trouble while trying to fetch each value later. You may loop, using a for + case ... esac, and store the values in associative arrays.
I think eval is what you need.
#!/bin/bash
arr_pos[0]=2;
arr_pos[1]=4;
arr_pos[2]=5;
eval "cat \$${arr_pos[1]}"
For me that works.

BASH Accessing the next 'field' in command line without a loop

This may seem like a silly question, but I've looked high and low for a solution and have not come up with one. I have a script that accepts a number of arguments and I simply wish to know how to have one argument and access the next immediate argument. I've tried stuff like $i+1 with no avail. This next argument will end up holding a destination directory, so I need to be able to access the contents. Also note that I am getting the argument position through a counter variable iterated in a loop, which is why I can't simply say $2 or something. I apologize for such a mundane question.
$ bash -c 'foo=1 ; echo ${#:$((foo+2)):1}' a b c d e f g
d
If i is a variable containing an integer, and you want the (i+1)st argument, you can do:
eval arg=\$$(( i + 1 ))
Now arg contains the argument you want
It might not be applicable in your case, but if you are accessing the arguments sequentially, you could use the inbuilt shift command to shift through the arguments.
Doing shift with no argument will set $1 to $2, $2 to $3 and so on, discarding the old value of $1. Giving a numerical argument to shift will shift that many steps.

Resources