Why do I get this error using {1..9} in zsh? - shell

I run the following code
zgrep -c compinit /usr/share/man/man{1..9}/zsh*
I get
zsh: no matches found: /usr/share/man/man2/zsh*
This is strange, since the following works
echo Masi{1..9}/masi
This suggests me that the problem may be a bug in Zsh.
Is the above a bug in Zsh for {1..9}?

It's not a bug, and it is working inside words fine. The trouble you're having here is that {1..9} is not a wildcard expression like * is; as your echo example shows, it's an iterative expansion. So your zgrep example is exactly the same as if you had typed each alternate version into the command line, and then since there are no man pages starting with zsh in man2, it errors out. (It's erroring out on a failure to find a match, not anything intrinsically related to your brace sequence expansion.)
If you did this, on the other hand:
zgrep -c compinit /usr/share/man/man[1-9]/zsh*
you'd get the results you expect, because [1-9] is a normal wildcard expression.

In zsh, if you want to use ranges in filenames, zle offers <1-n> on any real names it can expand on. That is to say:
$ touch a0b a1b a5b a7b
$ print a<0-100>b
And then hit <Tab> right after the final b would leave you with print a0b a1b a5b a7b expanded on the line.
For all other intents and purposes - perhaps full range requirements, non-file and scripting use - I'd express this using the rather succinct idiomatic zsh loop as:
for n ({1..50}); do print $n; done
Will allow you process the whole sequence range of numbers 1 to 50 :) after which you can do all sorts of useful things with, such as a file collection that doesn't exist yet:
arr=($(for n ({1..50}); do print /my/path/file$n.txt; done)) && print $arr[33]

Related

Get the contents of an expanded expression given to eval through Bash internals

I'm writing some shell functions that allow to print stack traces when errors occur. For this I'm using the BASH_LINENO array which contain the line number for each frame. Then I retrieve the line from the file using BASH_SOURCE array and a subprocess like line="$(tail -n+$lineno "$file" | head -n1)".
Anyway, it works well, except when an error occur within an eval. The problem is that the line number corresponds to the line after the expression given to eval has been expanded. Therefore, when I retrieve the line with head and tail, obviously it's now the wrong one, or it's not a line at all (lineno is superior to the number of lines in the file).
So I wonder how I could get the actual expanded line. I looked at the variables provided by Bash, but none seems to help in this case.
Example, script1.sh:
#!/usr/bin/env bash
eval "$(./script2.sh)"
script2.sh:
#!/usr/bin/env bash
echo
echo
echo
echo false
When I hit the false line when executing script1.sh, the line number I get is 4, and the file source I get is script1.sh, so it's wrong.
When the line is out of the file, I could detect it, and print the first previous eval line instead, but it's very hacky and I'm sure there are a few different cases to handle. And if the line is within the file, then I cannot even know if it's the right one or not.
eval is hell :'(
Ideally, the BASH_COMMAND would be an array as well, and I could retrieve the commands from it instead of reading the files.
Another idea I just have would be to force the user to pipe the result of the expression into a command that will compress it on one line. Any ideas how, or programs to do that? A simple join on ";" seems to naive (again, lots of edge cases).
P.S.: sorry for the title, I have difficulty giving a meaningful title to this one :/
Eventually I found a workaround: by overriding the eval command with my own function, I was able to change the way I print the stack trace for errors happening in eval statements.
eval() {
# pre eval logic
command eval "$#"
# post eval logic
}
Anyway, please don't use eval, or if you do, use only one line arguments:
# GOOD: "easy" to deal with
for i in ...; do
eval "$(some command)"
done
# BAD: this will mess up your line numbers
eval "$(for i in ...; do
some command $i
done)"

For loop in shell script - colons and hash marks?

I am trying to make heads or tails of a shell script. Could someone please explain this line?
$FILEDIR is a directory containing files. F is a marker in an array of files that is returned from this command:
files=$( find $FILEDIR -type f | grep -v .rpmsave\$ | grep -v .swp\$ )
The confusing line is within a for loop.
for f in $files; do
target=${f:${#FILEDIR}}
<<do some more stuff>>
done
I've never seen the colon, and the hash before in a shell script for loop. I haven't been able to find any documentation on them... could someone try and enlighten me? I'd appreciate it.
There are no arrays involved here. POSIX sh doesn't have arrays (assuming you're not using another shell based upon the tags).
The colon indicates a Bash/Ksh substring expansion. These are also not POSIX. The # prefix expands to the number of characters in the parameter. I imagine they intended to chop off the directory part and assign it to target.
To explain the rest of that: first find is run and hilariously piped into two greps which do what could have been done with find alone (except breaking on possible filenames containing newlines), and the output saved into files. This is also something that can't really be done correctly if restricted only to POSIX tools, but there are better ways.
Next, files is expanded unquoted and mutalated by the shell in more ridiculous ways for the for loop to iterate over the meaningless results. If the rest of the script is this bad, probably throw it out and start over. There's no way that will do what's expected.
The colon can be as a substring. So:
A=abcdefg
echo ${A:4}
will print the output:
efg
I'm not sure why they would use a file directory as the 2nd parameter though...
If you are having problems understanding the for loop section, try http://www.dreamsyssoft.com/unix-shell-scripting/loop-tutorial.php

11*(...) as a bash parameter without quotation marks

I'm trying to write a small piece of code that passes a small formula to another program, however i've found that something strange happens when the formula starts with 11*(:
$ echo 11*15
Neatly prints '11*15'
$ echo 21*(15)
Neatly prints '21*(15)', while
echo 11*(15)
Only gives '11'. As far as I've found this only happens with '11*('. I know that this can be solved by using proper quotation marks, but I'm still curious as to why this happens.
Does anyone know?
How is your program coded? If its coded to take in parameters, then pass your formula like
./myprogram "11*15"
or
echo '11*15' | myprogram
If you do echo just like that on the command line, you may inadvertently display files that has 11 in its file name
11*(15) uses a Bash-specific extended glob syntax. You've stumbled across it accidentally, emphasizing why quotation marks are a good idea. (I also learned a lot tracking down why it was working differently for me; thanks for that.)
The behavior of
echo 11*(15)
in bash is going to vary depending on whether extglob is enabled. If it's enabled *(PATTERN-LIST) matches zero or more occurrences of the patterns. If it's disabled, it doesn't, and the resulting ( is likely to cause a syntax error.
For example:
$ ls
11 115 1155 11555 115555
$ shopt -u extglob
$ echo 11*(55)
bash: syntax error near unexpected token `('
$ shopt -s extglob
$ echo 11*(55)
11 1155 115555
$
(This explains the odd behavior I discussed in comments.)
Quoting from the bash 4.2.8 documentation (info bash):
If the `extglob' shell option is enabled using the `shopt' builtin,
several extended pattern matching operators are recognized. In the
following description, a PATTERN-LIST is a list of one or more
patterns separated by a `|'. Composite patterns may be formed using
one or more of the following sub-patterns:
`?(PATTERN-LIST)'
Matches zero or one occurrence of the given patterns.
`*(PATTERN-LIST)'
Matches zero or more occurrences of the given patterns.
`+(PATTERN-LIST)'
Matches one or more occurrences of the given patterns.
`#(PATTERN-LIST)'
Matches one of the given patterns.
`!(PATTERN-LIST)'
Matches anything except one of the given patterns.

Tricky brace expansion in shell

When using a POSIX shell, the following
touch {quick,man,strong}ly
expands to
touch quickly manly strongly
Which will touch the files quickly, manly, and strongly, but is it possible to dynamically create the expansion? For example, the following illustrates what I want to do, but does not work because of the order of expansion:
TEST=quick,man,strong #possibly output from a program
echo {$TEST}ly
Is there any way to achieve this? I do not mind constricting myself to Bash if need be. I would also like to avoid loops. The expansion should be given as complete arguments to any arbitrary program (i.e. the program cannot be called once for each file, it can only be called once for all files). I know about xargs but I'm hoping it can all be done from the shell somehow.
... There is so much wrong with using eval. What you're asking is only possible with eval, BUT what you might want is easily possible without having to resort to bash bug-central.
Use arrays! Whenever you need to keep multiple items in one datatype, you need (or, should use) an array.
TEST=(quick man strong)
touch "${TEST[#]/%/ly}"
That does exactly what you want without the thousand bugs and security issues introduced and concealed in the other suggestions here.
The way it works is:
"${foo[#]}": Expands the array named foo by expanding each of its elements, properly quoted. Don't forget the quotes!
${foo/a/b}: This is a type of parameter expansion that replaces the first a in foo's expansion by a b. In this type of expansion you can use % to signify the end of the expanded value, sort of like $ in regular expressions.
Put all that together and "${foo[#]/%/ly}" will expand each element of foo, properly quote it as a separate argument, and replace each element's end by ly.
In bash, you can do this:
#!/bin/bash
TEST=quick,man,strong
eval echo $(echo {$TEST}ly)
#eval touch $(echo {$TEST}ly)
That last line is commented out but will touch the specified files.
Zsh can easily do that:
TEST=quick,man,strong
print ${(s:,:)^TEST}ly
Variable content is splitted at commas, then each element is distributed to the string around the braces:
quickly manly strongly
Taking inspiration from the answers above:
$ TEST=quick,man,strong
$ touch $(eval echo {$TEST}ly)

Bash: select a previous command that matches a pattern

I know about the bash history navigation with the Up and Down arrows.
I would like a lazy way to select a previous command that matches some regex (that is shorter than the whole command, so it takes less time to be typed).
Is it possible with bash?
If not, do other shells have such a feature?
You can always use CTRL-R to search your history backward, and type some part of the previous command. Hitting CTRL-R again (after the first hit) repeats your query (jumps to the next match if any).
Personally I use this for regex search (as regex searching is not possible yet (AFAIK)):
# search (using perl regexp syntax) your entire history
function histgrep()
{
grep -P $# ~/.bash_history
}
Edit:
For searching most recent history items via that function, see this (on setting $PROMPT_COMMAND ).
Zsolt, avoid hard-coded filenames, use the HISTFILE variable instead, with a fallback if you're really paranoid: ${HISTFILE:-~/.bash_history} ;-)
Any why grepping directly through the history file?! You'd lose the history number, which is necessary to replicate the command (e.g. !33 to execute again the 33th entry from your history) without having to copy&paste grep's output.
Please keep in mind that using that kind of $# expansions may fail at various (epic) levels. For instance, an argument beginning with "-" (histgrep -h) will usually hang, or shoot yourself in the foot. Indeed, this basic example may be worked around easily, following the classic "--" way of separating arguments from options, but the discussion has no ending, remembering that the arguments to be provided to that hack would be regular expressions. ;-)
Oh, and isn't histgrep somehow a too verbose choice? h, i, Tab, g, Tab :)
IMHO I'd stick to using ^R, falling back to history | grep ... whenever necessary.
Anyway, for the sake of the example, I'd (lazily) rewrite this little helper as:
function hgrep() { history | grep -P -- "$*"; }
See my answer here
Example:
$echo happy
happy
$!?pp?
happy
I prefere this solution since one can see all the contents of the whole history in addition to search by regex, and the regex-type is according to the very good regex engine of vim:
vim -u /root/.vimrc -M + <( history )
This I have given in this my post:
https://unix.stackexchange.com/questions/718828/search-in-whole-bash-history-list-with-full-featured-regex-engine
Regards
Anton Wessel

Categories

Resources