0

In a bash script, I want to define a commandstring that specifies options and primaries for find, based on arguments that I pass to the script. Then I want to use exec to invoke find with these options and primaries.

In general, I want to be able to define any invocation of find that I could run on the command line, and exec it inside a subshell. And in particular, I want to be able to exclude filenames containing linefeed=LF=newline=$'\n'.

When I invoke the sample script below with argument easy, it prints all filenames containing whitespace, and exec works as expected. But if I invoke it with argument hard, then exec does not work as expected.

As you see below, if I hard-code the command find . -name *$'\n'*, it works as expected. But if I put it into commandstring, it does not. Is there some way to fix this?

I do not have a higher version of bash available, am stuck with version 3 on my macOS.

The script, called junk:

#!/bin/bash  
set -f
bash --version

if [[ ${1} = easy ]]
then
    printf '***%s***\n' "${1}"
    commandstring="find .  -name *[[:space:]]* "
elif [[ ${1} = hard ]]
then
    printf '***%s***\n' "${1}"
    commandstring="find . -name *$'\n'* "
else
    printf '%s\n' "first argument=${1} but must be either easy or hard."
    exit 1
fi
declare -p commandstring; printf '%s\n' "commandstring=${commandstring}";

printf -- '-------------------------------\n%s\n' "hard-coded hard commandstring inside this script:"
find . -name *$'\n'* 

printf -- '-------------------------------\n%s\n' "hard-coded easy commandstring inside this script:"
find .  -name *[[:space:]]* 

printf -- '-------------------------------\n%s\n' 'Now test exec with the commandstring, inside $( subshell )'

junk=$( 
    {
        set -f
        printf '%s\n' "Now inside subshell, will exec"
        declare -p commandstring; printf '%s\n' "commandstring=${commandstring}";
        exec $commandstring  
    } > /dev/stderr ; 
)

printf '%s\n' 'got past $( subshell ); now try to exec it inside the script'
exec $commandstring  
printf '%s\n' 'should never get here, because it comes after exec'

How it behaves:

> find . -type f
./easy
./newlineBEF
newlineAFT
./tabBEF    tabAFT
./okay
./spaceBEF  AFTspace
./zzz
> ls -lT ./*
-rw-r--r--  1 BNW  staff  10 Oct 19 20:15:36 2019 ./easy
-rw-r--r--  1 BNW  staff  11 Oct 20 12:08:24 2019 ./newlineBEF?newlineAFT
-rw-r--r--  1 BNW  staff  13 Oct 19 20:15:43 2019 ./okay
-rw-r--r--  1 BNW  staff  14 Oct 20 12:37:19 2019 ./spaceBEF  AFTspace
-rw-r--r--  1 BNW  staff  10 Oct 19 20:14:01 2019 ./tabBEF?tabAFT
-rw-r--r--  1 BNW  staff   0 Feb 26 11:30:54 2019 ./zzz
> junk easy
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.
***easy***
declare -- commandstring="find .  -name *[[:space:]]* "
commandstring=find .  -name *[[:space:]]* 
-------------------------------
hard-coded hard commandstring inside this script:
./newlineBEF
newlineAFT
-------------------------------
hard-coded easy commandstring inside this script:
./newlineBEF
newlineAFT
./tabBEF    tabAFT
./spaceBEF  AFTspace
-------------------------------
Now test exec with the commandstring, inside $( subshell )
Now inside subshell, will exec
declare -- commandstring="find .  -name *[[:space:]]* "
commandstring=find .  -name *[[:space:]]* 
./newlineBEF
newlineAFT
./tabBEF    tabAFT
./spaceBEF  AFTspace
got past $( subshell ); now try to exec it inside the script
./newlineBEF
newlineAFT
./tabBEF    tabAFT
./spaceBEF  AFTspace
> junk hard
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.
***hard***
declare -- commandstring="find . -name *\$'\\n'* "
commandstring=find . -name *$'\n'* 
-------------------------------
hard-coded hard commandstring inside this script:
./newlineBEF
newlineAFT
-------------------------------
hard-coded easy commandstring inside this script:
./newlineBEF
newlineAFT
./tabBEF    tabAFT
./spaceBEF  AFTspace
-------------------------------
Now test exec with the commandstring, inside $( subshell )
Now inside subshell, will exec
declare -- commandstring="find . -name *\$'\\n'* "
commandstring=find . -name *$'\n'* 
got past $( subshell ); now try to exec it inside the script
> 
Jacob Wegelin
  • 1,304
  • 11
  • 16
  • 1
    Too much work, keep command line arguments in an array instead. There was a dupe for that but can't find it now – oguz ismail Oct 21 '19 at 17:43
  • Possible duplicate of [this question](https://stackoverflow.com/questions/12136948/why-does-shell-ignore-quotes-in-arguments-passed-to-it-through-variables). It's marked as a duplicate of yet another question, but that one has `eval` as top answer, and `eval` is a really bad idea. Also, see [BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!](http://mywiki.wooledge.org/BashFAQ/050) – Gordon Davisson Oct 21 '19 at 18:15

1 Answers1

0

Thanks to the answers: The following script behaves as desired.

#!/bin/bash  
set -f
bash --version
argARRAY=( "$@" )
declare -p argARRAY
find "${argARRAY[@]}"

Two invocations:

> junk .
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.
declare -a argARRAY='([0]=".")'
.
./easy
./newlineBEF
newlineAFT
./tabBEF    tabAFT
./okay
./spaceBEF  AFTspace
./zzz
> junk  . -name *$'\n'*
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.
declare -a argARRAY='([0]="." [1]="-name" [2]="*
*")'
./newlineBEF
newlineAFT
Jacob Wegelin
  • 1,304
  • 11
  • 16