7

The following works when pasted directly into my bash terminal (I call bash explicitly, bash version: 4.4.19(1)-release (x86_64-pc-linux-gnu))

for filename in /home/dean/Downloads/!(*example).txt; do
    echo "${filename}"
done

This command echoes back all of the txt files that do not have 'example' in the filename.

But when I convert this into a script called temp.sh, chmod +x temp.sh and call it by ./temp.sh:

#!/usr/bin/env bash

for filename in /home/dean/Downloads/!(*example).txt; do
    echo "${filename}"
done

I get the following error:

dean@dean-thinkpad-p52s:~/Downloads$ ./temp.sh 
./temp.sh: line 3: syntax error near unexpected token `('
./temp.sh: line 3: `for filename in /home/dean/Downloads/!(*example).txt; do'

I fail to understand the problem here. Why is it doing exactly what I want in the shell but not in the script.

Edit (to answer panki's question):

The difference between when env is called in shell/terminal and when env is called in shell/script:

dean@dean-thinkpad-p52s:~/Downloads$ diff example_myshell.txt example_called_script.txt 
5a6
> _=/usr/bin/env
36,37d36
< TERM=xterm-256color
< SHELL=/bin/bash
38a38,39
> SHELL=/bin/bash
> TERM=xterm-256color
45c46
< PYENV_SHELL=bash
---
> SHLVL=4
47c48
< SHLVL=3
---
> PYENV_SHELL=bash
61d61
< _=/usr/bin/env

dnk8n
  • 233
  • 1
  • 6
  • Does it work if you source the script? Is the bash returned by `env` the same as your shell? – Panki May 27 '19 at 09:56
  • Sourcing the script works, but I need it to work when calling the script. I don't know if I follow your second question but I have included the difference of when `env` is called in script and when it is called in shell. – dnk8n May 27 '19 at 10:03

1 Answers1

14

The !(...) Korn shell extended operator is only available in bash when you turn the extglob option on (it is off by default).

You may have extglob turned on in your interactive shell via ~/.bashrc or other initialization file, but notice that those files are not sourced when running scripts, and that option is not inherited from the calling shell (unless the BASHOPTS variable in the environment, but it would be a bad idea to have it there).

Explicitly turning it on with

shopt -s extglob

at the beginning of your script should work.

Notice that the shopt -s extglob only has effect beginning with the next line which wasn't already parsed. This means that you cannot use shopt -s extglob like set -f, to only turn the extended patterns on in a subshell:

# this won't work
(
  shopt -s extglob
  echo !(no such file)
)

You'd have to do something like:

(
  shopt -s extglob
  eval 'echo !(no such file)'
)
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • 1
    @DeanKayton, the (confusingly named) `shopt` command and the `extglob` option are "standard" in `bash`, not in `sh`. If you want a `sh` equivalent, you can always do `for f in /path/to/*.txt; do case ${f##*/} in (*example.txt) continue; esac; ...; done` – Stéphane Chazelas May 27 '19 at 10:16
  • I made the mistake of putting `shopt -s extglob` command immediately before the `!(...)` Korn shell extended operator was needed. This did not work at all, the command works when it is on the first line of the script. Can you comment as to why that is the case? – dnk8n May 28 '19 at 08:40
  • 2
    That's a bug in bash. `shopt -s extglob` only has effect beginning with the next line. There's really nothing you can do about it, than putting them on separate lines. –  May 28 '19 at 08:52
  • Notice that the extglob patterns in `bash` are not really like those from `ksh93`; you cannot write something like `touch bar baar baaar baaaar; echo b{2,3}(a)r` to do regex-like quantifiers. –  May 28 '19 at 09:19
  • Commenting on the latest edit, the final paragraph. I think I put it in a function (immediately before the place where I needed its functionality applied. Would that explain why it did not work? Because the function already got parsed as a whole? – dnk8n May 29 '19 at 07:18
  • 2
    Yeah, you cannot turn it on in a function, because a function gets parsed as a single unit, just like the `(...)` subshell from my example. (And btw, the `shopt` options, unlike the `set` options, cannot be made local to the function with `local -`, anyways). Another similar quirk/bug is with the `failglob` option -- a failed glob will zap the whole line/parsing unit, not just the command it's used in, and not even an `eval` will help with it ;-) `(shopt -s failglob; eval 'echo hehe*'; echo DONE)` –  May 29 '19 at 21:02