need help in unix for iterating a for loop - unix

The bellow line code is not working.it is not iterating through the directory.
input={20132802,20132802}
for i in $(ls -1 /home/$input/*s.log)
do
...
done
but when providing the input in the loop working fine.
for i in $(ls -1 /home/{20132802,20132802}/*s.log)
do
...
done
please help.

Brace expansion cannot be used in variables because it is performed before any other expansions.
From man bash:
Brace expansion is performed before any other expansions, and any
characters special to other expansions are preserved in the result. It
is strictly textual. Bash does not apply any syntactic interpretation
to the context of the expansion or the text between the braces. To
avoid conflicts with parameter expansion, the string ‘${’ is not
considered eligible for brace expansion.
However, you can use eval to overcome this limitation:
input={20132802,20132802}
for i in $(eval ls -1 /home/$input/*s.log)
do
...
done
Obligatory reading: eval is evil

Try writing ${input} instead of $input since i is the index of the for loop and I guess $i is used when you write $input ('$i'nput).

Related

zsh command line processing - separating the last arguments from the previous ones

I am writing a zsh script, which is invoked with a variable number of arguments, such as
scriptname a b c d filename
Inside the script, I want first to loop over the arguments (except the last one) and process them, and finally do something with the processed data and the last argument (filename).
I got this working, but am not entirely happy with my solution. Here is what I came up with (where process and apply are some other scripts not relevant to my problem):
#!/bin/zsh
set -u
x=""
filename=$#[-1]
# Process initial arguments
for ((i=1; i<$#; i++))
do
x+=$(process ${#[$i]})
done
apply $x $filename
I find the counting loop too cumbersome. If filename where the first argument, I would do a shift and then could simply loop over the arguments, after having saved the filename. However I want to keep the filename as the last argument (for consistency with other tools).
Any ideas how to write this neatly without counting loop?
You can slice off the last argument from the original list and save them into an array, if thats an option
args=("${#:1:$# -1}")
for arg in "${args[#]}"; do # iterate over all, except the last
printf '%s\n' "$arg"
done
Using the array as a placeholder is optional as you can iterate over the arguments slice directly i.e. for arg in "${#:1:$# -1}"; do. The syntax is even available in bash also.
As pointed out by chepner's comment, you could use a zsh specifc syntax as
for arg in $#[1,-2]; do
printf '%s\n' "$arg"
done

Parsing variable in loop incorrectly [duplicate]

I want to run certain actions on a group of lexicographically named files (01-09 before 10). I have to use a rather old version of FreeBSD (7.3), so I can't use yummies like echo {01..30} or seq -w 1 30.
The only working solution I found is printf "%02d " {1..30}. However, I can't figure out why can't I use $1 and $2 instead of 1 and 30. When I run my script (bash ~/myscript.sh 1 30) printf says {1..30}: invalid number
AFAIK, variables in bash are typeless, so how can't printf accept an integer argument as an integer?
Bash supports C-style for loops:
s=1
e=30
for i in ((i=s; i<e; i++)); do printf "%02d " "$i"; done
The syntax you attempted doesn't work because brace expansion happens before parameter expansion, so when the shell tries to expand {$1..$2}, it's still literally {$1..$2}, not {1..30}.
The answer given by #Kent works because eval goes back to the beginning of the parsing process. I tend to suggest avoiding making habitual use of it, as eval can introduce hard-to-recognize bugs -- if your command were whitelisted to be run by sudo and $1 were, say, '$(rm -rf /; echo 1)', the C-style-for-loop example would safely fail, and the eval example... not so much.
Granted, 95% of the scripts you write may not be accessible to folks executing privilege escalation attacks, but the remaining 5% can really ruin one's day; following good practices 100% of the time avoids being in sloppy habits.
Thus, if one really wants to pass a range of numbers to a single command, the safe thing is to collect them in an array:
a=( )
for i in ((i=s; i<e; i++)); do a+=( "$i" ); done
printf "%02d " "${a[#]}"
I guess you are looking for this trick:
#!/bin/bash
s=1
e=30
printf "%02d " $(eval echo {$s..$e})
Ok, I finally got it!
#!/bin/bash
#BSD-only iteration method
#for day in `jot $1 $2`
for ((day=$1; day<$2; day++))
do
echo $(printf %02d $day)
done
I initially wanted to use the cycle iterator as a "day" in file names, but now I see that in my exact case it's easier to iterate through normal numbers (1,2,3 etc.) and process them into lexicographical ones inside the loop. While using jot, remember that $1 is the numbers amount, and the $2 is the starting point.

Process substitution =(list) in middle of argument

How can I use =(list)-style process substitution in the middle of an argument?
This works:
% echo =(echo)
/tmp/zshxxxxxx
So does this:
% echo =(echo):works
/tmp/zshxxxxxx:works
But this does not:
% echo broken:=(echo)
zsh: missing end of string
Notably, this also works:
% echo works:<(echo)
works:/proc/self/fd/11
The problem is =(list) can only stand at the beginning of arguments. Quoting from the ZSH manual:
The expression may be preceded or followed by other strings except
that, to prevent clashes with commonly occurring strings and patterns,
the last form [this is =(list)] must occur at the start of a command
argument, and the forms are only expanded when first parsing command
or assignment arguments.
I have a tool that accepts an argument of the form format:filename, and I need to use a real file, not a pipe, so I cannot use <(list). What is a reasonably simple and readable solution?
Use parameter expansion to "buffer" the process substitution.
% echo fixed:${:-=(echo)}
fixed:/tmp/zshxxxxxx
I have been trying to use the previous answer for a makefile, and it was not so trivial so here is my solution.
The initial problem is that with MinGW, the command line length is quite limited and it will get truncated in case of a very long object list, so I need to use the #file syntax for gcc, which allow to provide the arguments in a file.
SHELL := /bin/zsh
myprog.exe: very.o long.o list.o of.o obj.o files.o ...
gcc -o $# #$${:-=(<<< \"$^\")}
There is an alternate solution by using an anonymous function called immediatly :
myprog.exe: very.o long.o list.o of.o obj.o files.o ...
() { gcc -o $# #$$1 } =(<<< "$^")

Changing the global “path” from within a function?

My zshenv file has a bunch of lines like
if [[ -d "$HOME/bin" ]]; then
path=($HOME/bin $path)
fi
I thought I’d try to factor this pattern out into a function. I replaced it with
function prepend_to_path_if_exists() {
if [[ -d $1 ]]; then
path=($1 $path)
fi
}
prepend_to_path_if_exists("$HOME/bin")
but this gives the error
/Users/bdesham/.zshenv:8: missing end of string
where line 8 is the one where I’m calling prepend_to_path_if_exists. What exactly is causing this error, and how can I make this function work? I’m using zsh 5.0.5 on OS X 10.10.1.
You could call functions as with usual command executions like this (without ()):
prepend_to_path_if_exists "$HOME/bin"
It seems that zsh try to expand the glob prepend_to_path_if_exists(…) rather than to call the function.
TL;DR: Prepending emelemnts to $path would be accomplished by a little cryptic way:
(I'm not quite sure that the below form is preferable for anyone though.)
# `typeset -U` uniqify the elements of array.
# It could be good for $path.
typeset -U path
# prepending some paths unconditionally,
path[1,0]=(\
$HOME/bin \
$HOME/sbin \
)
# then filtering out unnecessary entries afterward.
path=(${^path}(-/N))
The $path[x,0]=… is prepending(splicing) element(s) to array taken from the below:
So that's the same as VAR[1,0]=(...) ? It doesn't really "look" very
much like prepend to me.
-- Greg Klanderman (http://www.zsh.org/mla/workers/2013/msg00031.html)
The ${^path}(-/N) expands the glob qualifires -/N on the each $path elements.
(Without ^ in the parameter expansion, the last elements of array will be evaluated, so it is mandatory in this case.)
The glob qualifires -/N means that "symbolic links and the files they point to"(-) the "directory"(/). And when it does not match anything do not raise errors (N).
In short, it would keep exsisting directories only for $path.

How can I implement the command 'ls' with wildcard, '*'?

EDIT #1 : I'm under the limit that all arguments are enclosed in two quotes, so that shell do not expand any argument with * to the corresponding path.
EDIT #2 : In order to retrieve directories such as */*, ../*, and dirA/*/file.out, How should I use iteration loop or recursive call?
I have just learned about the function fnmatch(). But I don't know start place.
There are many possible cases. I'm confused dealing with these all cases.
For example, Let me assume that executable program is a.out.
$./a.out -l */*
$./a.out -l ../*
$./a.out -l [file_name] [directory_name]
/* Since I also have to implement ls command with no wildcard. */
What should I do? Any advice would be awesome.
Thank you in advance.
Your problem is : shell replaces wildcard caracter * with all of the filenames matching the pattern.
Solution:
If you do not want to use this feature of bash, just put quotation marks around your command line arguments.
Calling your program that way will have the original arguments, containing wildcards.
After this, you can list all the filenames with their paths. For example using some recursive algorithm. Then you can apply some matching to these path string. (when visiting it)
If you want to be a good unix citizen, the rule is Don't do filename globbing unless you are writing a shell.
You want to write an ls-like program? Don't do any wildcard expansion. Don't treat "*" specially. Just treat your argv as a list of filenames. If your program handles these cases:
./a.out file1
./a.out file1 file2 file3
Then it will also handle
./a.out file*
correctly because the shell will do the expansion and your program won't need to know about it. And besides that, it will handle this:
zsh% ./a.out **/file<40-185>~file<90-100>(.mm-30OL[1,2])
which in zsh expanded glob syntax means: expand file40 through file185, except for file90 through file100, include only the ones that have been modified in the last 30 minutes, and use only the largest 2 files in the resulting set.
fnmatch is never going to do anything like that. But these fancy globs can be used with any command that just takes a filename list and doesn't care where it came from.
When you're in a situation where you can't take a list of filenames from the command line, then consider using fnmatch. ls isn't one of those situations.

Resources