How to turn globbing on and off?

14,040

Solution 1

If you want globs to be disabled only while the shell is interpreting code in any.sh, with bash4.4+ or ash-based shells, you can do:

x() {
  local -
  set -o noglob
  . any.sh
}

Or in zsh:

x() {
  set -o localoptions -o noglob
  . any.sh
}

That is use a function instead of an alias (you don't want to use aliases for several commands as that doesn't do what you want when you do cmd | x or cmd && x for instance), make sure changes to options (to the $- variable as one way to look at it) are local to the function, disable glob and source the file.

With older versions of bash, you could do:

x() {
  local ret restore
  [[ $- = *f* ]] || restore='set +o noglob'
  set -o noglob
  . any.sh
  ret=$?
  eval "$restore"
  return "$ret"
}

Or maybe a more generic helper function like:

withopt() {
  local ret option
  local -a restore
  option=$1; shift
  [[ -o $option ]] || restore=(set +o "$option")

  set -o "$option"
  "$@"
  ret=$?
  "${restore[@]}"
  return "$ret"
}

And then, you can use an alias if you like like:

alias x='withopt noglob . any.sh'

Note that it doesn't prevent * from being expanded if you do:

x *

As the noglob option ends up being enabled long after that command has been evaluated. For that, see my other answer (an answer to a different question).

Solution 2

This was posted before the question was clarified, and now addresses a different need. I'm still leaving it here as that can be useful to others.

I suppose you want to be able to do:

x *.txt

and the *.txt to be passed unexpanded to any.sh and globs to be reenabled afterwards.

You can't do that with bash. Use zsh instead where you can do:

alias x='noglob any.sh`

Where noglob disables aliases only for that command.

$ echo /etc/p*d
/etc/pam.d /etc/passwd /etc/profile.d
$ noglob echo /etc/p*d
/etc/p*d

Note that it affects the expansion of globs in arguments of that echo commands only. * would still be expanded in noglob echo $(echo *) or noglob eval 'echo *' or noglob . some-script where some-script does a echo *.

Actually, there may be a way with bash:

oneshot_noglob() {
  case $- in
    (*f*) ;; # globs already disabled
    (*) set -f; shot=0; debug_trap=$(trap -p DEBUG)
        trap '
          if ((++shot == 2)); then
            set +f
            trap - DEBUG
            '"$debug_trap"'
          fi' DEBUG;;
  esac
}

alias x='oneshot_noglob; any.sh'

Which uses the DEBUG trap to restore set +f after one command has been executed after the set -f.

Now, with all aliases that contain more than one command, that has a few caveats.

echo foo | x

Becomes:

echo foo | oneshort_noglob; any.sh

So the output of echo is only fed to oneshort_noglob.

Same for things like:

cmd && x

Where any.sh would be executed regardless of whether cmd is successful or not.

Also note that it affects all globs in every level of subshell until just before the second command is being executed in the main shell process.

For instance, in x $(echo *; echo *) *, none of those * would be expanded because the DEBUG trap is not inherited unless you set the extdebug option.

Solution 3

One may change something temporarily and then try to return it back to the state it was before. But I propose better, cleaner, more reliable and better maintainable approach: just make needed change local. Bash allows you to easily achieve that by using a subshell (more info here - Grouping Commands).

In your case it can be done like this

alias x='( set -f;. any.sh ; )'
Share:
14,040

Related videos on Youtube

John Goofy
Author by

John Goofy

Updated on September 18, 2022

Comments

  • John Goofy
    John Goofy over 1 year

    Within my ~./bashrc in an alias I have turned of globbing like this.

    alias x='set -f;. any.sh'
    

    But which command enables globbing again or should I set this options in any.sh?

    Any response is welcome.

    • John Goofy
      John Goofy over 6 years
      @GeorgeVasiliou Actually I wrote to the end of any.sh the command set +f and it works. Are there different issues in your or my approach?
    • George Vasiliou
      George Vasiliou over 6 years
      Same result , different approach. In my approach commands are chained by && which means that the exit code of previous command must be "success" = "0" for the next command to run. By your approach set +f can be present anywhere in your script. You can re-enable globbing at will , even if the any.sh script will finally "fail".
    • Basile Starynkevitch
      Basile Starynkevitch over 6 years
      @JohnGoofy: Please edit your question to motivate it. Why do you want to disable globbing? Can't you use something different than a Posix shell (e.g. Python, Awk, scsh, ....) ?
    • John Goofy
      John Goofy over 6 years
      @GeorgeVasiliou I figured out, that your approach won't work for me. I left my alias as posted and added set +f to the end of any.sh.
    • Stéphane Chazelas
      Stéphane Chazelas over 6 years
      In any case, set -f; any.sh; set +f or set -f && any.sh && set +f don't make much sense as there's no glob to be expanded in between that set -f and that set +f.
    • John Goofy
      John Goofy over 6 years
      @StéphaneChazelas I know, I tried this before I asked my question.
    • Victor Yarema
      Victor Yarema almost 3 years
      Why would someone want to switch something on after some work if it would be better to simply return back to state which was there before. By going further I propose to simply make that option change local for that specific task. Encapsulation at its best. As for me the simplest way to achieve that is to use a subshell. Just put a part of script in brackets and all options changes which happen inside will be discarded after it. For example: alias x='( set -f;. any.sh ; )'.
    • axd
      axd over 2 years
      Ah, the eternal struggle against globbing. I have been battling it for my entire career. this is what I use to feed e.g. git branch: ``` PATTERN=echo $PATTERN --list *$1* ```
  • John Goofy
    John Goofy over 6 years
    I have made an edit to my alias. I forgot the source .. I have tried your approach with and without source. However, if my alias looks Iike I have posted and I write to the end of my script set +f I can pass wildcard arguments and my bash is still able for globbing.
  • John Goofy
    John Goofy over 6 years
    Thank you very much for your effort. But I have found a solution, just appending set +f to my file and it works.
  • John Goofy
    John Goofy over 6 years
    By the way, even without the . in my alias, your approach won't work for me as I mentioned.
  • Stéphane Chazelas
    Stéphane Chazelas over 6 years
    @JohnGoofy, as I said, that's to be able to call any.sh * with a litteral argument passed to any.sh as that's the only way I could make sense of your question before you added the .. It doesn't answer your question now that you've clarified it.
  • Hauke Laging
    Hauke Laging over 6 years
    (f) does not match a word which consists of more than f. You need *f*
  • John Goofy
    John Goofy over 6 years
    Dear fellow. I already have an easy and short solution. I prepend set -f to my alias and I put set +f to my script and It works. Are there some issues to use your approach? If not, I don't know if I should close, delete or answer my self this question.
  • John Goofy
    John Goofy over 6 years
    I wrote a little file manager for the CLI. Because there are wildcards I need both, globbing and not globbing within one task and of course after executing the script I want globbing again.
  • Peter Cordes
    Peter Cordes over 6 years
    @JohnGoofy: Normally you should just write your script to quote anything that you don't want glob-expansion to act on. e.g. "$foo" instead of $foo
  • Stéphane Chazelas
    Stéphane Chazelas over 5 years
    @Sergio, there, you only need to quote that * shell wildcard operator: find . -name '*.cpp' Or find . -name '[*].cpp' if you want to find a file called literally *.cpp.
  • Victor Yarema
    Victor Yarema almost 3 years
    @roaima, if OP used . (source) command as an optimization to avoid spawning another shell subprocess then yes, subshell is not an answer. But it is also worth mentioning that this kind of performance degradation can be noticed in cases like calling scripts in a loop or similar. If user start this from command line by issuing explicit command manually then the performance degradation should be neglectible. Taking into account that this is an alias I assumed that OP plans to call it manually. I may be wrong.
  • Victor Yarema
    Victor Yarema almost 3 years
    @roaima and thank you for bringing up concerns. Those are really valid in some cases. And it also turned into some elaboration from my side which I forgot (was lazy) to add initially.