Prepending some text to each "wildcard" argument in bash

462 Views Asked by At

A simple Example: mybin *.txt will expand to mybin a.txt b.txt c.txt

But I'm looking for a simple solution to have an expansion to something like: mybin --conf a.txt --conf b.txt --conf c.txt.

Is there a built in to do this? What is the simplest way for this?

5

There are 5 best solutions below

2
On BEST ANSWER

find is my friend:

mybin $(find /wherever/ -name '*.txt' -printf '--conf %p ')
0
On

A bit tricky solution:

eval mybin "--conf\ {`echo *.txt|tr -s " " ,`}"
4
On

for all txt files

eval mybin "$(printf -- '--conf %q ' *.txt)"

If only for certain txt files

eval mybin '--conf "'{a,b,c}.txt'"'

Maybe we should use a wrapper function. This is not a builtin solution, but it works well than the previous two commands if filenames contain spaces or special characters.

function mybinw:

function mybinw() {
  declare -a mybin_opts

  for file in "$@"; do
    mybin_opts+=(--conf "$file")
  done

  mybin "${mybin_opts[@]}"
}

Test:

mybin:

#!/bin/bash

for q in "$@"; do
  echo "=> $q"
done

Create some txt files, some file names include spaces or special characters

touch {a,b,c,d,efg,"h h"}.txt 'a(1).txt' 'b;b.txt'

For all txt files:

eval mybin "$(printf -- '--conf %q ' *.txt)"
=> --conf
=> a(1).txt
=> --conf
=> a.txt
=> --conf
=> b;b.txt
=> --conf
=> b.txt
=> --conf
=> c.txt
=> --conf
=> d.txt
=> --conf
=> efg.txt
=> --conf
=> h h.txt

for certain txt files:

eval mybin '--conf "'{a,b,c,"h h"}.txt'"'
=> --conf
=> a.txt
=> --conf
=> b.txt
=> --conf
=> c.txt
=> --conf
=> h h.txt

Using a wrapper function

touch 'c"c.txt'

mybinw *.txt
=> --conf
=> a(1).txt
=> --conf
=> a"b.txt
=> --conf
=> a.txt
=> --conf
=> b;b.txt
=> --conf
=> b.txt
=> --conf
=> c"c.txt
=> --conf
=> c.txt
=> --conf
=> d.txt
=> --conf
=> efg.txt
=> --conf
=> h h.txt
0
On
# usage mix command switch args ...
mix(){
        p=$1; shift; q=$1; shift; c=
        i=1; for a; do c="$c $q \"\${$i}\""; i=$((i+1)); done
        eval "$p $c"
}

mix mybin --conf *.txt

This is portable to any POSIX shell, not just bash, and is able to handle filenames with spaces, special characters, etc:

$ qecho(){ for a; do echo "{$a}"; done; }
$ touch 'a;b' "a'b" "a\\'b" 'a"b' 'a\"b' '(a b)' '(a    b)' 'a
b'
$ mix qecho --conf *
{--conf}
{(a    b)}
{--conf}
{(a b)}
{--conf}
{a
b}
{--conf}
{a"b}
{--conf}
{a'b}
{--conf}
{a;b}
{--conf}
{a\"b}
{--conf}
{a\'b}
0
On
set -- *.txt

for thing do
    shift
    set -- "$@" --conf "$thing"
done

mybin "$@"

This would use the list of positional parameters ($@) to hold the expanded glob pattern. We then loop over these items and modify $@ by inserting --conf before each. The mybin utility can then be invoked with this list.

The quoting in the code is deliberate to stop the shell from splitting any strings on whitespaces and from expanding any filename globs if they occur as proper parts of the filenames that *.txt matches.

A bash-specific variation:

files=( *.txt )

for thing in "${files[@]}"; do
    args+=( --conf "$thing" )
done

mybin "${args[@]}"

Shorter variations of both of the above. First for /bin/sh:

set --
for thing in *.txt; do
    set -- "$@" --conf "$thing"
done

mybin "$@"

Then for bash:

for thing in *.txt; do
    args+=( --conf "$thing" )
done

mybin "${args[@]}"

As a shell function:

delim_run () {
    cmd=$1
    delim=$2

    shift 2

    for thing do
        shift
        set -- "$@" "$delim" "$thing"
    done

    "$cmd" "$@"
}

You would then be able to do

delim_run mybin --conf *.txt