How to delete all except a directory in Makefile? - bash

The following command set is working on linux prompt.
%cd ${ADIR}/exe; shopt -s extglob; rm -rf !(BDIR)
But it is not working in Makefile
Linux command - works
%cd ${ADIR}/exe; shopt -s extglob; rm -rf !(BDIR)
Command in Makefile
#cd ${ADIR}/exe; shopt -s extglob; rm -rf !\(BDIR\)
Make file message
rm: cannot remove `!(BDIR)': No such file or directory

The problem with your Makefile is that it escapes ( and ), which makes the shell interpret them literally.
The second issue,
/bin/sh: -c: line 0: syntax error near unexpected token `('
is caused by make using sh to execute commands, not bash.
The !(...) wildcard syntax (and extglob) are only supported by bash, not sh.
You could call bash explicitly:
#bash -c 'cd ${ADIR}/exe; shopt -s extglob; rm -rf !(BDIR)'
But that doesn't work either, because extglob doesn't take effect until the next line of input has been read, so !( ) still throws a syntax error.
We need a way to run a multi-line command using a single invocation of the shell. Unfortunately make makes this unnecessarily complicated.
One possible solution:
SHELL = /bin/bash
...
#bash -c $$'cd ${ADIR}/exe; shopt -s extglob\nrm -rf !(BDIR)'
This tells make to use bash to execute all recipes (not /bin/sh). We then run bash again manually, but using $'...' to quote the command string. This lets us write \n to embed a literal newline, which makes extglob / !( ... ) work.
We need double $$ to escape the $ for make, so $'...' becomes $$'...'.
I'm not very happy with this solution.

Unfortunately there's a weird behavior of bash in that the shopt setting won't take effect until the newline, so any globbing on the same line won't recognize it. Try this at your shell prompt:
$ shopt -s extglob; echo !(BDIR)
bash: !: event not found
Then try it in two lines:
$ shopt -s extglob
$ echo !(BDIR)
...works
Unfortunately this means it's almost impossible to use this with make.
You should use the POSIX-compatible version suggested in triplee's comment and avoid the need for special shells altogether.
Oh, it seems the answer was deleted. Anyway, do something like this instead:
foo:
cd ${ADIR}/exe && for f in *; do \
case $$f in (BDIR) : ok ;; (*) rm -rf "$$f" ;; esac; \
done

Related

Shell globbing negation doesn't work in bash

I'm currently confused why shell globbing in terminal works with negation but shows an error when running in bash.
Take the commands executed in the terminal below, which shows all js files within the ./HTML directory except for js files that ends with .bundle.js.
$ shopt -s globstar
$ ls ./HTML/**/!(*.bundle).js
The command above works perfectly, now let's put it in a bash file
list-js.sh
#!/usr/bin/env bash
shopt -s globstar
ls ./HTML/**/!(*.bundle).js
Executing it in a terminal:
$ bash list-js.sh
list-js.sh: line 4: syntax error near unexpected token `('
list-js.sh: line 4: `ls ./HTML/**/!(*.bundle).js'
As you can see, it shows a syntax error.
globstar only enables the ** pattern. The extglob option allows !(...). Somewhere in your interactive shell, that has already been enabled (perhaps in your .bashrc, perhaps you typed shopt -s extglob earlier). However, it needs to be enabled explicitly in your script, since such settings are not inherited from the shell that starts the script.
#!/usr/bin/env bash
shopt -s globstar extglob
ls ./HTML/**/!(*.bundle).js
(As an aside, ** without globstar does not cause a syntax error because it is treated simply as two adjacent *s, the second one being redundant.)

Why does pattern "*.so?(.*)" produce a syntax error in a script but not on command line?

The pattern is a little unusual because I added the trailing "?(.*)" portion. It works on command line as I expected but I get a syntax error for the same in a script.
$ bash --version
GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)
...
$ cat x.sh
touch a.so a.so.1
ls *.so?(.*)
rm *.so?(.*)
$ touch a.so a.so.1
$ ls *.so?(.*)
a.so a.so.1
$ rm *.so?(.*)
$ ls
x.sh
$ bash x.sh
x.sh: line 2: syntax error near unexpected token `('
x.sh: line 2: `ls *.so?(.*)'
$
You are using an extended glob but these aren't enabled by default within a script. In order to using them, they must be explicitly enabled. You can do so by adding this before the line:
shopt -s extglob
To disable them later on in the script, you can use shopt -u extglob.
As chepner rightly points out, this feature isn't enabled by default in the interactive shell, either. Presumably, this line is present either in one of your system-wide startup scripts or one of your personal ones.

Shell script isn't working

I can't find out what's happening. Is this regular expression not working for shell scripts?
sudo rm -R -f '/web/!(release)'
Any ideas?
thanks
Perhaps you forgot to enable extglob. Also you shouldn't quote your extended glob pattern:
shopt -s extglob
sudo rm -R -f '/web/'!(release)
Also if the shell you're calling sudo with is not able to access /web, you can wrap up your command with bash:
sudo bash -c "shopt -s extglob"$'\n'"rm -R -f '/web/'!(release)"
See Pattern Matching and Filename Expansion.
Script file
shopt -s extglob
sudo rm -R -f 'web/'!(release);
Result
script.sh: Syntax error: "(" unexpected
edit :
I was running with "sh" instead of bash
now with bash script.sh it works
thanks

glob pattern in bash don't recognize '('

When running the following command:
rm -rf !(file1|file2)
all files except file1 and file2 are removed; as intended.
When either placing this command in a bash script:
#!/bin/bash
rm -rf !(file1|file2)
or running it using bash -c:
bash -c "rm -rf !(file1|file2)"
I receive the following error:
syntax error ner unexpected token '('
I have tried setting the shell options using
shopt -s extglob
yeilding in:
bash -c "shopt -s extglob; rm -rf !(file1|file2)"
to enable glob according to:
https://superuser.com/questions/231718/remove-all-files-except-for-a-few-from-a-folder-in-unix and some other questions as well.
Still it doesn't work, and I'm at loss.
First of all, for safety, let's do our testing with echo !(file1|file2) instead of rm -rf !(file1|file2).
Anyway, bash does some parsing of the entire command line before executing the shopt -s extglob command. When bash encounters the (, the extglob option isn't set yet. That's why you get the error.
Try this instead:
bash -O extglob -c 'echo !(file1|file2)'
In your script, you just need to turn on the option as a separate command line before relying on it:
#!/bin/bash
shopt -s extglob
echo !(file1|file2)
You can actually do this with the -c flag also:
bash -c 'shopt -s extglob
echo !(file1|file2)'
Or even like this:
bash -c $'shopt -s extglob\necho !(file1|file2)'

What does shopt -s dirspell do?

I cannot figure it out, i understand shopt -s cdspell but cannot find out what shopt -s dirspell does.
The Bash Reference Guide says:
dirspell
If set, Bash attempts spelling correction on directory names during word completion if the directory name initially supplied does not exist.
I tried several times on several directories but that is not the behavior.
I'm using bash 4.2.10(2) on i386-apple-darwin10.7.0
From the change-log
x. There is a new shell option: `dirspell'. When enabled, the filename
completion code performs spelling correction on directory names during
completion.
Let's try:
$ ls
spam/
$ cat spam/test
hello world
without dirspell
$ cat span/test [tab]
# nothing happens
with dirspell
$ shopt -s dirspell
$ cat span/test [tab]
#line is replaced by
$ cat /home/user/tmp/shopt/spam/test
If you set the shell options direxpand and dirspell, then the tab-completion does work.

Resources