Blake Regalia
Blake Regalia

Reputation: 2766

bash: 'map' function arguments?

What is the most elegant way to essentially 'map' a list of bash arguments through some transformation (such as concatenating each string) before forwarding the list onto some other command? Using xargs comes to mind but I can't seem to conceptualize how to do this.

function do_something {
    # hypothetically
    for arg in "$@"; do
        arg="$arg.txt"
    done

    command "$@"
}

do_something file1 file2 file3

Such that the result would be to call command file1.txt file2.txt file3.txt.

Upvotes: 4

Views: 4278

Answers (3)

Zoey Hewll
Zoey Hewll

Reputation: 5385

You can use the following definition for a map that is similar to the one found in many functional programming languages (eg python, haskell):

function map
{
    local f="$1"
    shift # consume first argument
    for arg
    do
        "$f" "$arg" # assuming `f` prints a single line per call
    done
}

Here's how you would use it in your example. Here some_cmd may be a function defined locally:

function do_something
{
    local IFS=$'\n' # only split on newlines when word splitting
    result=($(map suffix "$@")) # split into lines and store into array
    some_cmd "${result[@]}" # call some_cmd with mapped arguments.
}
function suffix
{
    echo "$@".txt
}

do_something file1 file2 file3

Here's another variation of writing do_something. Here some_cmd must exist in $PATH:

function do_something
{
    map suffix "$@" | xargs some_cmd # call some_cmd with mapped arguments. 
}

The main downside is that to use the result in another function, you need to mess around with IFS to split on the newlines, or pipe into xargs; and if your map outputs contain newlines then either method fails completely.

Upvotes: 3

In order to "forward" arguments to other commands, there are several ways. Try this script:

printargs() {
  echo "Args for $1:"
  shift
  for a in "$@"; do
   echo "    arg: -$a-"
  done
}

printargs dolstar $*
printargs dolstarquot "$*"
printargs dolat $@
printargs dolatquot "$@"

and invoke it with test aguments:

./sc.sh 1 2 3
Args for dolstar:
arg: -1-
arg: -2-
arg: -3-
Args for dolstarquot:
arg: -1 2 3-
Args for dolat:
arg: -1-
arg: -2-
arg: -3-
Args for dolatquot:
arg: -1-
arg: -2-
arg: -3-

Things go a little differently if an argument contains spaces:

./sc.sh 1 "2 3"
Args for dolstar:
arg: -1-
arg: -2-
arg: -3-
Args for dolstarquot:
arg: -1 2 3-
Args for dolat:
arg: -1-
arg: -2-
arg: -3-
Args for dolatquot:
arg: -1-
arg: -2 3-

The dolatquot "$@" is the only version that correctly forwards arguments. Otherwise, as seen in another answer, you can manipulate arguments and construct a new list via arrays or a single string.

Upvotes: 0

ffledgling
ffledgling

Reputation: 12140

What you've done is mostly correct, except that you'll need to use an array to store the new arguments:

function do_something {
    array=()
    for arg in "$@"; do
        array+=("$arg.txt")
    done

    command "${array[@]}"
}

do_something file1 file2 file3

Upvotes: 5

Related Questions