29

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.

David
  • 3,392
  • 3
  • 36
  • 47
Nordanfors
  • 371
  • 1
  • 4
  • 9

4 Answers4

56

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.

starfry
  • 9,273
  • 7
  • 66
  • 96
14

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
Jay Taylor
  • 13,185
  • 11
  • 60
  • 85
devnull
  • 118,548
  • 33
  • 236
  • 227
  • Thank you, that is a better way to get rid of the last argument. My issue is that there seems to be something different about $@ compared to $array that qualifies it as valid input for the application I am passing it to. I just can't figure out what it is. – Nordanfors Dec 05 '13 at 11:42
  • @user1047256 Try passing `${array[@]}` to your application. – devnull Dec 05 '13 at 11:48
  • I tried what you suggested but still no go. Fyi I changed my nick to Nordanfors. :-) – Nordanfors Dec 05 '13 at 12:30
  • @Nordanfors How do you pass parameters to your __application__ otherwise? (Could you update the question with the information?) – devnull Dec 05 '13 at 12:36
  • Your help is most appreciated. All that was missing was double quotes around the array to get the application to process it. Thanks! – Nordanfors Dec 05 '13 at 14:38
  • 2
    @Nordanfors _Always_ quote variables. – devnull Dec 05 '13 at 14:41
  • 1
    The next answer should be accepted, not this one. It has triple the upvotes. – Noumenon Dec 05 '18 at 23:32
  • @Noumenon: **Bienvenue StackOverflow.** The accepted answer declining to abject uselessness over time is the common case, rendering this StackOverflow feature an anti-pattern in UX design. The evolving democratic wisdom of the crowds `>>>>` the non-democratic and rapidly obsoleted decision of a single individual. – Cecil Curry Dec 31 '18 at 06:43
  • There's an advantage to this approach: The "pop" operation becomes O(1), so you can put it in a loop. Allocating the array is still O(n) and slower than using the subarray approach, but this has its merits. – phicr Sep 08 '20 at 17:42
11

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 11
    Code `"${@:1:${#}-1}"` is exactly what makes full list of arguments like in "$@" without the last one. – uvsmtid Mar 14 '15 at 09:49
  • Right. The sketchy part is using 0 as the first argument to avoid having to subtract 1 from the second. – chepner Mar 14 '15 at 11:33
  • Actually, index 0 includes non-argument - path to the script being executed (a value automatically provided by shell). You are not avoiding subtraction of 1 - the last argument is still included and you must subtract 1. Using 0 is simply adding one more (likely not required) value to the list. – uvsmtid Mar 14 '15 at 11:38
  • 1
    It would appear this worked in `bash` 3.2 due to a bug that was subsequently fixed. – chepner Mar 14 '15 at 11:43
1
set -- "${@:1:$#-1}"

sets the parameter list to first up to penultimate, removing the last one

jaam
  • 900
  • 4
  • 23