LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Bash command substitution but with bash variables (https://www.linuxquestions.org/questions/programming-9/bash-command-substitution-but-with-bash-variables-4175736565/)

gerard4143 04-30-2024 04:25 AM

Bash command substitution but with bash variables
 
I have a simple bash script that prompts the user for a command and a filename and it works with this input:

Enter command: ls
Enter filename: datafile

but if I enter:
Enter command: ls -l
Enter filename: datafile

Then the program fails with:
line 10: ls -l: command not found

Code:

#! /usr/bin/env bash                                                                                                                   
set -euo pipefail                                                                                                                     
IFS=$'\n\t'                                                                                                                           
                                                                                                                                       
read -r -p 'Enter command: ' cmd                                                                                                       
read -r -p 'Enter filename: ' fName                                                                                                   
                                                                                                                                       
#IFS=$' \n\t'                                                                                                                         
                                                                                                                                       
ans=$(${cmd} "${fName}")                                                                                                               
                                                                                                                                                                                                                                                                       
echo "${ans}"

If I remove the comment on the second IFS=$' \n\t' then the command substitution will work with both ls and ls -l.

What's going on here?

pan64 04-30-2024 05:10 AM

I wanted to write, it is quite simple, it is what you have requested. But probably not that obvious:
IFS is the Input Field Separator, you set it to \n\t. Therefore ls -l is a single word (does not contain either \n or \t).
Actually there is no such command, "ls -l" is not a command itself.
BUT
if IFS is set to $' \n\t': ls -l will be split (because it contains a space, which is now a separator) and the result is: the command will be ls and -l will be an argument to it, which is the correct way to execute it.

NevemTeve 04-30-2024 07:55 AM

Code:

- ans=$(${cmd} "${fName}")                                                                                                               
+ ans=$(eval ${cmd} "'${fName}'")


gerard4143 04-30-2024 07:45 PM

I tried a few experiments in my bash terminal..

If I type ls -l and hit return it produces the expected results.
Code:

ls -l
If I type 'ls' '-l' and hit return it produces the same results.
Code:

'ls' '-l'
And if I type '''ls''' '''-l''' and hit return(surprisingly) it produces the same results.
Code:

'''ls''' '''-l'''
But if I type 'ls -l' and hit return?
Code:

'ls -l'
ls -l: command not found


NevemTeve 04-30-2024 11:41 PM

Regarding '''ls''' '''-l'''
Try to remove the empty strings:
Code:

$ echo "'''ls''' '''-l'''" | sed "s/''//g"
'ls' '-l'

Off:
As far as I know, there is no way to use quotes within quotes (duplication or escaping), you have to close the quote, use a standalone quote (\' or "'"), then again a staring quote:
Code:

$ echo 'she sometimes said '\''Supercalifragilisticexpialidocious'\'' I remembered'
she sometimes said 'Supercalifragilisticexpialidocious' I remembered

With double quotes escaping works:
Code:

$ echo "another version of this word is \"supercaliflawjalisticeexpialadoshus\" you might add"
another version of this word is "supercaliflawjalisticeexpialadoshus" you might add

Off/2 this is different in other languages, eg:
Code:

echo 'This works in \'PHP\' language';
SELECT 'This works in ''SQL'''


pan64 05-01-2024 02:02 AM

you can use set -x to see what's going on.
you type a string, press enter. First this string will be evaluated, and split into an array and finally will be executed
Code:

pan@host:/tmp/a$ ls -l
+ ls -l
total 0
pan@host:/tmp/a$ 'ls' '-l'
+ ls -l
total 0
pan@host:/tmp/a$ '''ls''' '''-l'''
+ ls -l
total 0
pan@host:/tmp/a$ 'ls -l'
+ 'ls -l'
bash: ls -l: command not found

What is probably not obvious, ls -l is always split into two parts, the only exception is the last case when you force it to keep in one (that is 'ls -l', with ', which is still not part of the string, just printed to show it)

gerard4143 05-01-2024 02:15 AM

Quote:

Originally Posted by pan64 (Post 6499210)
What is probably not obvious, ls -l is always split into two parts, the only exception is the last case when you force it to keep in one (that is 'ls -l', with ', which is still not part of the string, just printed to show it)

This was the revelation to me(but shouldn't have been one). All the early Bash chapters point out this behavior but somehow I never made the connection for this example. Thanks for pointing it out.

MadeInGermany 05-08-2024 05:59 AM

By quoting you force it to be one word, no word-splitting takes place.
Differerent quoting styles:
Code:

'ls -l'
"ls -l"
ls\ -l

The shell invokes a one-word command with an embedded space.
The same applies to the command arguments:
Code:

touch "two words"
ls -l 'two words'
rm two\ words

The shell recognizes the one-word argument, dequotes it, and hands it to the respective command (first word).

Code:

'''ls''' '''-l'''
is a concatenation of many '' strings, like ''+'ls'+''<space>''+'-l'+''

The word-splitting is according to $IFS (Input Field Separators).
With IFS=$'\n\t' the space is removed but the \t (tab) is still there. Then
Code:

ls -l
is one word, but
Code:

ls<tab>-l
is two words.
What happens with the \n (newline)? On the command line (actually readline, also read) a newline causes an end-of-input-line so it won't exist for a direct word-splitting.
But the following example demonstrates it:
Code:

twolines="line1
line2"
echo "with word-splitting"
echo $twolines
echo 'double quotes "" allow $-evaluation/substitution but not word-splitting:'
echo "$twolines"

Instead of
echo ...
you better use
printf "<%s>\n" ...
for clarity; the format is applied on each argument (wrapped in < > followed by a newline).


All times are GMT -5. The time now is 06:03 AM.