Doubts with for loop in Unix - bash

Sorry if its sounds dumb but I'm currently studying for an exam to apply for a job (administrative one) and I learnt about java, php, python but not much about unix scripts. I can see it's very similar to php in structure and I have many questions about it .
One of them is this loop, what does it do and why does it output b*?
for var in b*
echo $var
done
I tried this code and the terminal output was b*, Why does this code answer me with text (in this case, b*) or does something else happen?

A for loop in shell scripting takes each whitespace separated string from the input and assigns it to the variable given, and runs the code once for each value. In your case b* is the input and $var is the assigned variable.
The shell will also perform expansion on the input. Basically, the * acts like a wildcard for any part of a file name. So if you have files beginning with b in the current directory, those will get looped over:
$ ls
bean.java bean.class readme.txt
$ for var in b*; do echo $var; done
bean.java
bean.class
If however you don't have any files beginning with b (so that your pattern b* doesn't match anything) the pattern will remain unexpanded:
$ rm bean.*
$ ls
readme.txt
$ for var in b*; do echo $var; done
b*

Related

When using a path string in a shell script, it would be trans to a string containing children

Here is some bash code
path='*'
echo $path
If I execute the code in terminal directly, the output is
*
But if I put it in a shell script e.g., test.sh. Then I execute the test.sh, the output will be like this
test.sh file1 file2 file3
I got a string contains all items under the path.
Why the two outputs is different? If I want the second output to be the first one, which means do not trans a path string when I use it, what should I do?
I tested a little. You could change your script to this:
path='*'
echo "$path"
and it will print * instead of files if you run it as a bash script.
Seems echo can be used as a alternative of ls command:
echo * means list all files under current folder.
echo *.jpg will list all files with jpg as suffix, and if there is no such file in current folder, echo *.jpg will just print "*.jpg".
I think this answer should be posted as a comment, since I don't really know why echo command behave like this. But I do not have enough reputation to add comment, so...

more elegant way to do array using indirect reference in bash [duplicate]

What I have is this:
progname=${0%.*}
progname=${progname##*/}
Can this be nested (or not) into one line, i.e. a single expression?
I'm trying to strip the path and extension off of a script name so that only the base name is left. The above two lines work fine. My 'C' nature is simply driving me to obfuscate these even more.
Bash supports indirect expansion:
$ FOO_BAR="foobar"
$ foo=FOO
$ foobar=${foo}_BAR
$ echo ${foobar}
FOO_BAR
$ echo ${!foobar}
foobar
This should support the nesting you are looking for.
If by nest, you mean something like this:
#!/bin/bash
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
echo ${${HELLO}WORLD}
Then no, you can't nest ${var} expressions. The bash syntax expander won't understand it.
However, if I understand your problem right, you might look at using the basename command - it strips the path from a given filename, and if given the extension, will strip that also. For example, running basename /some/path/to/script.sh .sh will return script.
The following option has worked for me:
NAME="par1-par2-par3"
echo $(TMP=${NAME%-*};echo ${TMP##*-})
Output is:
par2
An old thread but perhaps the answer is the use of Indirection:${!PARAMETER}
For e.g., consider the following lines:
H="abc"
PARAM="H"
echo ${!PARAM} #gives abc
This nesting does not appear to be possible in bash, but it works in zsh:
progname=${${0%.*}##*/}
Expressions like ${${a}} do not work. To work around it, you can use eval:
b=value
a=b
eval aval=\$$a
echo $aval
Output is
value
Actually it is possible to create nested variables in bash, using two steps.
Here is a test script based upon the post by Tim, using the idea suggested by user1956358.
#!/bin/bash
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
# This command does not work properly in bash
echo ${${HELLO}WORLD}
# However, a two-step process does work
export TEMP=${HELLO}WORLD
echo ${!TEMP}
The output is:
Hello, world!
There are lots of neat tricks explained by running 'info bash' from the command line, then searching for 'Shell Parameter Expansion'. I've been reading a few myself today, just lost about 20 minutes of my day, but my scripts are going to get a lot better...
Update: After more reading I suggest this alternative per your initial question.
progname=${0##*/}
It returns
bash
There is a 1 line solution to the OP's original question, the basename of a script with the file extension stripped:
progname=$(tmp=${0%.*} ; echo ${tmp##*/})
Here's another, but, using a cheat for basename:
progname=$(basename ${0%.*})
Other answers have wandered away from the OP's original question and focused on whether it's possible to just expand the result of expressions with ${!var} but came across the limitation that var must explicitly match an variable name. Having said that, there's nothing stopping you having a 1-liner answer if you chain the expressions together with a semicolon.
ANIMAL=CAT
BABYCAT=KITTEN
tmp=BABY${ANIMAL} ; ANSWER=${!tmp} # ANSWER=KITTEN
If you want to make this appear like a single statement, you can nest it in a subshell, i.e.
ANSWER=$( tmp=BABY${ANIMAL) ; echo ${!tmp} ) # ANSWER=KITTEN
An interesting usage is indirection works on arguments of a bash function. Then, you can nest your bash function calls to achieve multilevel nested indirection because we are allowed to do nested commands:
Here's a demonstration of indirection of an expression:
deref() { echo ${!1} ; }
ANIMAL=CAT
BABYCAT=KITTEN
deref BABY${ANIMAL} # Outputs: KITTEN
Here's a demonstration of multi level indirection thru nested commands:
deref() { echo ${!1} ; }
export AA=BB
export BB=CC
export CC=Hiya
deref AA # Outputs: BB
deref $(deref AA) # Outputs: CC
deref $(deref $(deref AA)) # Outputs: Hiya
As there is already a lot of answer there, I just want to present two different ways for doing both: nesting parameter expansion and variable name manipulation. (So you will find four different answer there:).
Parameter expansion not really nested, but done in one line:
Without semicolon (;) nor newline:
progname=${0%.*} progname=${progname##*/}
Another way: you could use a fork to basename
progname=$(basename ${0%.*})
This will make the job.
About concatenating variable name
If you want to construct varname, you could
use indirect expansion
foobar="baz"
varname="foo"
varname+="bar"
echo ${!varname}
baz
or use nameref
foobar="baz"
bar="foo"
declare -n reffoobar=${bar}bar
echo $reffoobar
baz
I know this is an ancient thread, but here are my 2 cents.
Here's an (admittedly kludgy) bash function which allows for the required functionality:
read_var() {
set | grep ^$1\\b | sed s/^$1=//
}
Here's a short test script:
#!/bin/bash
read_var() {
set | grep ^$1\\b | sed s/^$1=//
}
FOO=12
BAR=34
ABC_VAR=FOO
DEF_VAR=BAR
for a in ABC DEF; do
echo $a = $(read_var $(read_var ${a}_VAR))
done
The output is, as expected:
ABC = 12
DEF = 34
It will work if you follow the bellow shown way of taking on intermediate step :
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
varname=${HELLO}WORLD
echo ${!varname}
The basename bultin could help with this, since you're specifically splitting on / in one part:
user#host# var=/path/to/file.extension
user#host# basename ${var%%.*}
file
user#host#
It's not really faster than the two line variant, but it is just one line using built-in functionality. Or, use zsh/ksh which can do the pattern nesting thing. :)
Though this is a very old thread, this device is ideal for either directly or randomly selecting a file/directory for processing (playing tunes, picking a film to watch or book to read, etc).
In bash I believe it is generally true that you cannot directly nest any two expansions of the same type, but if you can separate them with some different kind of expansion, it can be done.
e=($(find . -maxdepth 1 -type d))
c=${2:-${e[$((RANDOM%${#e[#]}))]}}
Explanation: e is an array of directory names, c the selected directory, either named explicitly as $2,
${2:-...}
where ... is the alternative random selection given by
${e[$((RANDOM%${#e[#]}))]}
where the
$((RANDOM%...))
number generated by bash is divided by the number of items in array e, given by
${#e[#]}
yielding the remainder (from the % operator) that becomes the index to array e
${e[...]}
Thus you have four nested expansions.
If the motivation is to "obfuscate" (I would say streamline) array processing in the spirit of Python's "comprehensions", create a helper function that performs the operations in sequence.
function fixupnames()
{
pre=$1 ; suf=$2 ; shift ; shift ; args=($#)
args=(${args[#]/#/${pre}-})
args=(${args[#]/%/-${suf}})
echo ${args[#]}
}
You can use the result with a nice one-liner.
$ echo $(fixupnames a b abc def ghi)
a-abc-b a-def-b a-ghi-b
eval will allow you to do what you are wanting:
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
eval echo "\${${HELLO}WORLD}"
Output: Hello, world

Bash for loop testing two boolean expressions

Below is a simple bash program. It takes file types as command line arguments and it queries the current directory and prints the files of the type specified.
I would like to be able to query two different file types and therefore need two boolean expressions to represent this.
Below is my code for querying just one file type
#!/bin/bash
for x in $(ls *$1); do
echo $x;
done
Now what I would like to be able to do is (in pseudocode)
command line args fileName .sh .c
for x in (current directory files of *.sh) OR (in current directory files of *.c) do
print .sh files
print.c files
done
I've tried using || and I get syntax errors I can not find any evidence of being able to use || for two expressions in for loop.
I've tried using two nested for loops but they do not work and yield errors.
Is there any way I can accomplish this using the same for loop system.
Thank you.
Sounds like you want something like:
for extension in "$#"; do
printf 'Files ending in %s:\n' "$extension"
printf '%s\n' *"$extension"
done
Loop through all arguments passed to the script and print all files ending in each extension + a newline character.
Note that printf is a much more useful tool than echo, as it allows you to control the format of each thing is prints.
ls doesn't do anything useful either here; it is the shell which expands the * to the list of files matching the pattern.

Assigning a variable (with wildcard) with parentheses versus none

I have a simple naive question, I've figured out how to make my script run but I'd like to know why it didn't work previously.
I was assigning a variable with a wildcard using syntax similar to:
var=$dir/$subj/name*text*text.nii.gz
I could call the proper filename with ls $file, but when I tried to substitute in $file as an input into a command line (using FSL for image processing), I got an error saying it couldn't find the file with wildcards in place.
However, when I assign the variable with parentheses:
var=($dir/$subj/name*text*text.nii.gz)
It runs just fine. I'm assuming there are other and probably better ways to do this, but I'm just wondering why the initial variable assignment didn't work, and what the optimal way to assign variables in this manner is.
Thanks!
Let's consider a directory with three files:
$ ls
file1 file2 file3
Now define a variable:
$ var=file*
We can see what is in var by using declare -p:
$ declare -p var
declare -- var="file*"
As you can see, var still has the * in it. This is because pathname expansion is not performed for variable assignments. Consequently, var will not always work as you may have wanted. For example:
$ ls "$var"
ls: cannot access file*: No such file or directory
Next, let's try creating an array:
$ var=(file*)
$ declare -p var
declare -a var='([0]="file1" [1]="file2" [2]="file3")'
As you can see, pathname expansion is performed on arrays. Consequently, the following does work:
$ ls "$var"
file1
But, note that, for an array, $var refers only to the first element. If you wanted to access all its entries, a more complex notation is needed:
$ ls "${var[#]}"
file1 file2 file3

Shell script for searching a string pattern in file name

Hi i want to write a script that will go to a directory with many files and search a filename e.g. test_HTTP_abc.txt for this and search for HTTP string pattern, if it contains this string then set a variable equal to something:
something like:
var1=0
search for 06
if it contains 06 then
var1=1
else
var1=0
end if
but in unix script . Thanks
Probably the simplest thing is:
if test "${filename#*HTTP}" = "$filename"; then
# the variable does not contain the string `HTTP`
var=0
else
var=1
fi
Some shells allow regex matches in [[ comparisons, but it's not necessary to introduce that sort of non-portable code into your script.
Like this?
var=0
if fgrep -q 06 /path/to/dir/*HTTP*
then
var=1
fi
fgrep will return 0 ("truth") if there is a match in one of the files, and non-true otherwise (including the case of no matching input files).
If you want a list of matching files, try fgrep -l.
Well, I'm not going to write the script for you, you have to learn :)
Its easy if you break it down into smaller tasks;
The ls command is for looking at a directorie's contents. You can also use the find command to be a bit more intuitive, like find /some/folder -name "*string*"
To sift through the output of a command. You could store the output of a command to a variable or at using pipes.
You can search this output with something like awk (link), grep (link) an so on.
Setting variables is easy also in bash; http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-5.html
foundit=1
Why don't you have a go at trying to solve this puzzle first rather than someone telling you :D Show us where you get stuck in the puzzle.

Resources