LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Software (https://www.linuxquestions.org/questions/linux-software-2/)
-   -   looking for some bash shell magic with globbing (https://www.linuxquestions.org/questions/linux-software-2/looking-for-some-bash-shell-magic-with-globbing-944918/)

markseger 05-14-2012 01:35 PM

looking for some bash shell magic with globbing
 
I realize by default that when I execute a command like "xyz *", the shell expands the * and to disable that behavior I can do one of two things:
- quote the * like this: xyz '*'
- turn off word expansion like this: set -f, before running my command

I prefer the latter because expecting users to use quotes is a pain and it in fact works just fine. BUT...

The trick is how to turn expansion back on after I execute xyz! I first tried an alias that did something like this:

cmd='set -f; xyz, set +f'

which works fine as long as xyz doesn't take any arguments, which it does! so then I thought I could turn off expansion in the alias and turn it back on from within xyz, which happens to be a perl script, something like:

`set +f`

but that would essentially start a new process and so the set would affect that process, not the one my perl script is running in.

has anyone successfully done this sort of thing? I gotta believe there's a standard way to do it.

-mark

colucix 05-14-2012 02:30 PM

You can try a shell function:
Code:

function xyz() {
  set -f
  /path/to/xyz $@
  set +f
}
export -f xyz

The function takes precedence over the command, so that you have to specify the full path of the command inside. Put the definition in a system-wide configuration file (e.g. /etc/bashrc, but the exact name depends on your system) and the trick is done.

David the H. 05-14-2012 02:36 PM

Nope, I can't think of any way to do it cleanly. As you've discovered, aliases only work to replace the initial command name with the substituted string, and there's no way to arrange arguments in arbitrary order.

The usual way to set up such commands is with a function, but in this case, the globs would still have to be quoted in order to pass them to the function unexpanded first, thus defeating the whole purpose of it. (sorry colucix ;))

The only option I can suggest that even comes close is to put set +f in your PROMPT_COMMAND variable, and then it would be executed after every command execution. Of course that would mean you could never keep globbing unset for more than a single command.

The final suggestion I could make is just to set up a simple command for quickly toggling the setting on and off, which you can run just before and after your xyz command:

Code:

# "gt" stands for "glob toggle"

alias gt='[[ $- == *f* ]] && { set +f ; echo "globbing on" ;} || { set -f ; echo "globbing off" ;}'

# a function would be more robust, however.

gt() { if [[ $- == *f* ]]; then set +f && echo "globbing on" ; else set -f && echo "globbing off" ; fi ; }


PS: Please use [code][/code] tags around your code and data, to preserve formatting and to improve readability. Please do not use quote tags, colors, or other fancy formatting.

David the H. 05-14-2012 02:51 PM

Hold on, I just had an inspiration. A combination of alias and function just might do the trick.

Code:


alias xyz='set -f ; xyzfunc'

xyzfunc() { command xyz "$@" ; set +f ; }

The alias turns off globbing, and runs the function, and the function runs the actual command, then follows up by resetting globbing.

markseger 05-14-2012 03:12 PM

I'm not sure I follow, but I think understand functionally what you're suggesting and it sounds promising. what is exactly is xyzfunc? a one line script? I'm not sure what it would consist of. who calls the xyzfunc function?

just to give a little perspective, I'm trying to build my own ls command, called sls, that does different sorts of things but still supports wild cards. so using your syntax if I build an alias like:

alias sls='set -f; xyzfunc'

what is the name of the file with xyzfunc in it and what exactly is in it?

-mark

colucix 05-14-2012 03:38 PM

Quote:

Originally Posted by David the H. (Post 4678223)
The usual way to set up such commands is with a function, but in this case, the globs would still have to be quoted in order to pass them to the function unexpanded first, thus defeating the whole purpose of it. (sorry colucix ;))

You're absolutely right, David. I should have mistaken my tests! :jawa:

chrism01 05-15-2012 12:56 AM

It seems to me this does what you want
Code:

set -f && ./t.sh * && set +f
P1 = *

where the content of t.sh is
Code:

echo "P1 = $1"
In other words it turns off globbing, passes the wildcard to the called prog un-expanded, then resets globbing in calling env.

markseger 05-15-2012 06:22 AM

> set -f && ./t.sh * && set +f

That's not what I want to do. I want to pass $1 to my script. Sometimes it will be an * and sometimes not. But I get the general idea and hopefully can get it to do what I want. Thanks.
-mark

markseger 05-15-2012 07:39 AM

I keep going back and forth on this. One minute I think it's working and the next it's not. I think the problem is it's a different process resetting f and so that applies to the new process, not the original one. In other words, I don't think it works.

If someone could get this to work I'd love to see an example.

-mark

David the H. 05-15-2012 08:10 AM

Quote:

Originally Posted by markseger (Post 4678237)
I'm not sure I follow, but I think understand functionally what you're suggesting and it sounds promising. what is exactly is xyzfunc? a one line script? I'm not sure what it would consist of. who calls the xyzfunc function?

It's really not that complicated, actually. It uses a shell function, a block of code, sort of a mini-script, that resides in memory rather than a file. They are generally much more flexible and robust than a simple alias, since they can accept and process input parameters just like a script can.

The main problem you have here is that you need to alter the environment of the parent shell before the command line that executes the main script is parsed, and then again afterwards. The only way you can do this is with a command that is executed inside that shell itself; there's no way to do it from inside a script's sub-environment.

This means you must use aliases and/or functions. And the only way I can think of to create a one-word command that will do exactly what you want in the main shell is to use both.

Only an alias is capable of expanding a single command name into multiple commands before the rest of the line is parsed (meaning it's the only way you can turn off globbing). So we have to start with an alias. But as you know, it is then subsequently unable run another command after any arguments that follow it.

So what we do is use the alias to launch a function instead, and use that function to run the subsequent commands in the required order.

To break it down...

Code:

alias xyz='set -f ; xyzfunc'
With this alias, when you enter, for example, the line:

Code:

xyz *.txt *.jpg
...it will replace the word "xyz" at the front with the defined substitution string, so the line you are actually running is this:

Code:

set -f ; xyzfunc *.txt *.jpg
Since this expands to two separate commands, globbing can be disabled before the second command is run, and the arguments remain literal. So the alias executes the xyzfunc function, with the two literal globbing values as arguments.

Now we've also previously defined the function xyzfunc like this:

Code:

xyzfunc() { command xyz "$@" ; set +f ; }
(just put it in your shell startup file along with the alias.)

"xyz" here is now the name of the actual script you want to run. The command keyword is added to ensure that it doesn't do any recursive processing of the alias name, and just runs the script directly. "$@" expands to a string of all the parameters passed to the function; here they become arguments to xyz. Since and "$@" is quoted (not to mention globbing is still off), they remain literal the whole time.

So the function launches two commands in sequence:

Code:

xyz *.txt *.jpg
set +f

...and so globbing is turned back on before it exits.

markseger 05-15-2012 09:47 AM

outstanding. I didn't realize there was a construct called a function in bash. guess I spend too much time living with perl ;)
-mark


All times are GMT -5. The time now is 08:47 AM.