LinuxQuestions.org
Review your favorite Linux distribution.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 04-30-2024, 04:25 AM   #1
gerard4143
Member
 
Registered: Jan 2008
Location: P.E.I. Canada
Posts: 31

Rep: Reputation: 4
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?
 
Old 04-30-2024, 05:10 AM   #2
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,962

Rep: Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332
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.
 
3 members found this post helpful.
Old 04-30-2024, 07:55 AM   #3
NevemTeve
Senior Member
 
Registered: Oct 2011
Location: Budapest
Distribution: Debian/GNU/Linux, AIX
Posts: 4,872
Blog Entries: 1

Rep: Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871
Code:
- ans=$(${cmd} "${fName}")                                                                                                                
+ ans=$(eval ${cmd} "'${fName}'")
 
Old 04-30-2024, 07:45 PM   #4
gerard4143
Member
 
Registered: Jan 2008
Location: P.E.I. Canada
Posts: 31

Original Poster
Rep: Reputation: 4
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
 
Old 04-30-2024, 11:41 PM   #5
NevemTeve
Senior Member
 
Registered: Oct 2011
Location: Budapest
Distribution: Debian/GNU/Linux, AIX
Posts: 4,872
Blog Entries: 1

Rep: Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871Reputation: 1871
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'''

Last edited by NevemTeve; 05-01-2024 at 12:04 AM.
 
Old 05-01-2024, 02:02 AM   #6
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,962

Rep: Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332Reputation: 7332
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)
 
2 members found this post helpful.
Old 05-01-2024, 02:15 AM   #7
gerard4143
Member
 
Registered: Jan 2008
Location: P.E.I. Canada
Posts: 31

Original Poster
Rep: Reputation: 4
Quote:
Originally Posted by pan64 View Post
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.
 
Old 05-08-2024, 05:59 AM   #8
MadeInGermany
Senior Member
 
Registered: Dec 2011
Location: Simplicity
Posts: 2,807

Rep: Reputation: 1207Reputation: 1207Reputation: 1207Reputation: 1207Reputation: 1207Reputation: 1207Reputation: 1207Reputation: 1207Reputation: 1207
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).
 
1 members found this post helpful.
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Command Substitution: $(command) vs `command` penguinbody Linux - Newbie 9 04-15-2016 07:00 AM
bash command substitution performed "as encountered"? ta0kira Programming 13 04-24-2008 01:16 AM
bash script command substitution and quoting brian4xp Linux - Software 8 02-05-2008 11:43 AM
BASH command substitution that starts with a pipe | Kristofer Programming 4 02-27-2007 05:52 PM
Bash Command Substitution dakensta Programming 5 11-30-2006 03:10 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 03:23 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration