I have a shell script which uses process substitution
The script is:
#!/bin/bash
while read line
do
echo "$line"
done < <( grep "^abcd$" file.txt )
When I run the script using sh file.sh I get the following output
$sh file.sh
file.sh: line 5: syntax error near unexpected token `<'
file.sh: line 5: `done < <( grep "^abcd$" file.txt )'
When I run the script using bash file.sh, the script works.
Interestingly, sh is a soft-link mapped to /bin/bash.
$ which bash
/bin/bash
$ which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 9 Jul 23 2012 /usr/bin/sh -> /bin/bash
$ ls -l /bin/bash
-rwxr-xr-x 1 root root 648016 Jul 12 2012 /bin/bash
I tested to make sure symbolic links are being followed in my shell using the following:
$ ./a.out
hello world
$ ln -s a.out a.link
$ ./a.link
hello world
$ ls -l a.out
-rwx--x--x 1 xxxx xxxx 16614 Dec 27 19:53 a.out
$ ls -l a.link
lrwxrwxrwx 1 xxxx xxxx 5 May 14 14:12 a.link -> a.out
I am unable to understand why sh file.sh does not execute as /bin/bash file.sh since sh is a symbolic link to /bin/bash.
Any insights will be much appreciated. Thanks.
When invoked as sh, bash enters posix
mode after the startup files are read. Process substitution is not recognized in posix mode. According to posix, <(foo) should direct input from the file named (foo). (Well, that is, according to my reading of the standard. The grammar is ambiguous in many places.)
EDIT: From the bash manual:
The following list is what’s changed when ‘POSIX mode’ is in effect:
...
Process substitution is not available.
Related
#!/bin/bash
echo "hello" > /dev/udp/localhost/9082
The script executes normally under current user, however bash redirect returns an error when launched with nohup:
$ nohup ./test_redir.sh &
$ nohup: ignoring input and appending output to ‘nohup.out’
[1]+ Exit 2 nohup ./test_redir.sh
$ cat nohup.out
./test_redir.sh: 1: ./test_redir.sh: cannot create /dev/udp/localhost/9082: Directory nonexistent
What can be changed to enable bash redirects with nohup?
The error message seems to be from dash (which does not support /dev/udp/) rather than bash:
$ bash -c 'date > /not-a-dir/file'
bash: line 1: /not-a-dir/file: No such file or directory
$ /bin/sh -c 'date > /not-a-dir/file'
/bin/sh: 1: cannot create /not-a-dir/file: Directory nonexistent
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 2020-11-11 11:33 /bin/sh -> dash
$
Double check if your script really starts with #!/bin/bash and has execution permission (chmod +x). Or try:
$ nohup bash /your/script.sh &
So I am new learning bash script and I came up with the following piece of code.
run_command() {
echo "+" "$#"
"$#"
}
I am confused on what "$#" means and why is it twice?
Thanks a lot for your time and have a great day.
Aagam Jain's got the answer. I will add some explanation that wouldn't fit in a comment section. I apologize for the verbosity.
Consider this example.
Showing parameters given to a script
test.sh:
echo "$1"
echo "$2"
Let's run this script and give it 2 parameters.
$> bash test.sh ls -l
Result:
ls
-l
First parameter ls, represented by $1, is echo'ed in the first line. Second parameter -l, represented by $2, is echo'ed in the second line.
Bash manual - let's see what it says
($#) Expands to the positional parameters, starting from one
See this: https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters
How does that impact our example? Let's change test.sh a bit.
Expanding parameters starting from one
test.sh:
echo "$#"
Let's run it.
$> bash test.sh ls -l
Result:
ls -l
$# listed both parameters in the same line one after the other. If you had 5 parameters, they'd be printed one after the other.
Let's change test.sh a bit more.
Adding a + to the echo
test.sh:
echo "+" "$#"
Let's run it.
$> bash test.sh ls -l
Result:
+ ls -l
That means, a + appeared before both parameters were printed.
Change test.sh a bit more.
Executing all provided parameters
test.sh:
echo "+" "#"
"$#"
Let's run this.
bash test.sh ls -l
Result:
+ ls -l
total 4
-rw-r--r-- 1 eapo users 0 Sep 23 19:38 file1
-rw-r--r-- 1 eapo users 19 Sep 23 19:38 test.sh
Great. As the commenters and Aagam mentioned, the script printed out what it was going to execute (using echo "+" "$#") and then executed the command. The "$#" basically is just doing ls -lh. Terminal just executes it as is.
Let's put a function in the script now.
Adding a function in the script
test.sh:
run_command() {
echo "+" "$#"
"$#"
}
run_command ls -l
Note that we are executing the function in the script itself instead of giving it on the command line
Let's run it.
bash test.sh
Result:
+ ls -l
total 4
-rw-r--r-- 1 eapo users 0 Sep 23 19:38 file1
-rw-r--r-- 1 eapo users 58 Sep 23 19:41 test.sh
Hope the examples walk you through how the script functions.
This prints the command and its output.
e.g.
run_command() {
echo "+" "$#"
"$#"
}
run_command ls
#output
#+ ls
#files_list_in_current_directory
$ sh
sh-3.2$ if
> ps -ef | grep apple ;
> then
> echo APPLE
> fi ;
lazer 7584 7571 0 04:36 pts/4 00:00:00 grep apple
APPLE
sh-3.2$ exit
exit
$ which sh
/bin/sh
$ /bin/sh -c if ps -ef | grep apple ; then echo APPLE fi ;
bash: syntax error near unexpected token `then'
$
As above, my simple if statement works as expected when executed line by line but gives me the following error when executed using sh -c:
bash: syntax error near unexpected token `then'
What am I missing here?
Your interactive shell will be escaping the invocation via sh -c. In particular it's taking everyting after the semi-colon as a new statement.
Quote everything that you're feeding to /bin/sh e.g.
$ /bin/sh -c "if ps -ef | grep apple ; then echo APPLE fi ;"
I think you may also need to delimit further using semi-colons given that you're condensing everything onto one line, and would perhaps suggest you could use a heredoc.
I am batch converting some pictures with a quick and dirty bash script using ufraw :
IFS=$'\n'
PICS="/media/disk/kevin/Images/";
for pic in $(find $PICS -name "*CR2");
do
ufraw-batch $pic --out-type jpg --size=2048 --overwrite --out-path=$PICS;
rm -f $pic
done;
IFS=" ";
It's running fine with the usual rights, but if I run it with :
sudo ./convert.sh
I got this strange error :
find: "/media/disk/kevi" : no such file or directory.
I made it :
IFS=$'\n'
PICS="/media/disk/kevin/Images/";
echo PICS;
Then I got surprisingly :
/media/disk/kevi /Images/
WTF ?
You're winding up with $IFS being "n"; it's not interpreting \n as a newline but as a meaninglessly escaped n. I vaguely suspect you're running into some sort of ill-documented protection of $IFS for root (since it's a well known exploit vector), but you might want to try embedding a literal newline instead of a symbolic one in your script using ctrl-v enter.
If you don't have an explict #! line in your script, chances are that the sudo-ed command is being run under /bin/sh rather than /bin/bash and, if you're running on a recent Linux system, chances are that /bin/sh is dash rather than bash. The maintainers of dash maintain that IFS is not supposed to interpret escape sequences (see, for instance, here).
$ more convert.sh
IFS=$'\n'
PICS="/media/disk/kevin/Images/";
echo $PICS;
ps
$ ./convert.sh
/media/disk/kevin/Images/
PID TTY TIME CMD
30827 pts/0 00:00:01 bash
32042 pts/0 00:00:00 bash
32043 pts/0 00:00:00 ps
$ sudo ./convert.sh
/media/disk/kevi /Images/
PID TTY TIME CMD
32044 pts/0 00:00:00 sh
32045 pts/0 00:00:00 ps
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 2009-08-06 19:10 /bin/sh -> dash
So the behavior you see is only indirectly related to running under root. You'd see the same if you explicitly used dash. And another way to work around the problem would be to include an explict #!/bin/bash in your script.
Your code seems to have a typo. If the actual code is IFS='\n' or IFS="\n", you've set the separator to a list of two characters, \ and n. If IFS=\n, then one char A POSIX shell doesn't interpret \n as LF. It has nothing to do with root.
$ BLAH='\n'
$ echo $BLAH
\n
$ BLAH="\n"
$ echo $BLAH
\n
$ BLAH=\n
$ echo $BLAH
n
$ BLAH="Both\n\and\aregone"
$ echo $BLAH
Both a d arego e
I have a Perl script that I run in Linux 64bit and it looks like this:
my $ret = `/my/cmd option1 option2 <(/my/cmd2 input)`
This works in bash, but when I try to execute the same command as a backtick in the Perl script, it complains:
sh: -c: line 0: syntax error near unexpected token `('
Any ideas?
I guess your /bin/sh is not linked to bash, try:
my $ret = `bash -c '/my/cmd option1 option2 <(/my/cmd2 input)'`
You can check what /bin/sh is linked to with:
% ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Apr 5 07:03 /bin/sh -> dash