Command Line for Loop to Change Variables in File - linux

I have a script that compiles and runs a piece of idl code. Looks like this,
arg1=$1
idl << EOF
.rnew testvalue_{arg1}.pro
testvalue_{arg1}.pro
EOF
I want to run a for loop from the command line is in which arg1 can take on different names. What I have so far is,
for arg1 in testvalue.sh; do arg1={'value1', 'value2'}; done
I don't think my logic is correct. What am I missing?

first, you need to place $ before variable name so bash knows that it is replaced
Also you just need to give testvalue.sh value, because it is copied from $1 to $arg1
But why I don't recommend creating new variables, just use $1 two times
So the testvalue.sh is:
idl << EOF
.rnew testvalue_$1.pro
testvalue_$1.pro
And the loop:
for arg in 'value1' 'value2'; do ./testvalue.sh $arg; done

I'm a little hazy on the exact details of the question, but it sounds like you want to put 'value1' and 'value2' inside the list of values the for-loop iterates:
for arg1 in value1 value2
do
idl <<EOF
.rnew testvalue_${arg1}.pro
testvalue_${arg1}.pro
EOF
done
Note that I've also changed {arg1} to ${arg1}, as the dollar sign is required to expand the variable.
A for loop in shell scripts will iterate over every value after the in keyword. In the example above it will set arg1 to value1, then execute the contents of the loop, then move on and set arg1 to value2 and execute again.
You can also store the values in a variable:
values_to_test="value1 value2"
for arg1 in $values_to_test
do
idl <<EOF
.rnew testvalue_${arg1}.pro
testvalue_${arg1}.pro
EOF
done
Bear in mind that this applies word splitting and path expansion to values_to_test, so you will need to ensure that none of the values contain question marks, square brackets, asterisks, spaces, tabs, or newlines.
If it worries you, you can disable path expansion (and thus allow use of question marks, square brackets and asterisks) by running set -f in the script before the loop runs.

Related

how to concatenate two variable values with different rows in linux?

I have two variable or files with strings like
a="p|q"
b="x y z"
b variable has three characters separate by space.
my question is how to generate output as using a shell command in Linux with separator as "|"
p|q|x
p|q|y
p|q|z
Not quite sure if I understand your question correctly. The following for-loop will give you the output you want:
for i in $b; do echo "$a|$i"; done
You can do it without a loop, but it's not recommended in general:
printf "$a|%s\n" $b
$a is expanded before the command is run, so your format string ends up as 'p|q|%s\n'
$b is also expanded and since it is unquoted, each word becomes a separate argument
So the command you run is:
printf 'p|q|%s\n' x y z
which produces the output you wanted.
Note that this is not ideal because:
In general, putting variables into a printf format string can lead to unexpected output (e.g. if the variable contains characters like %s).
Deliberately using unquoted variables reads like a typo to a lot of people, so you probably need to add a comment to explain that you're being clever.

Combining case changing and substring variable expansion

If you append ^ to a variable, Bash capitalises the first letter of its contents. (Similarly, , sends it to lowercase and doubling-up either of these applies the transformation to the whole string, rather than just the first letter.)
foo="hello world"
echo ${foo^} # Hello world
You can also do ${variable:position:length} to extract a substring:
echo ${foo:0:1} # h
So far, I haven't found a way to combine these without, obviously, creating a temporary variable. Is there a form where I can get just the capitalised first letter out of an arbitrary string?
It does not change the basic limitation you are seeing in terms of not being able to "chain" expansions, but you can assign the result of an expansion to the same variable and do away with the temporary variable.
For instance:
A=text
A="${A^}"
A="${A//x/s}"
echo "$A"
echoes "Test".
No. Parameter expansion operators do not compose, so if you want more than one side effect, you need a temporary variable (which can include overwriting the original value as shown by #fred) or an external tool to process the result of the expansion (as shown by #anubhava).
Your other alternative is to use a different shell that does support more complicated operations, like zsh:
% foo="hello world"
% % print ${(U)${foo:0:1}}
H
You can use tr with substring:
tr [[:lower:]] [[:upper:]] <<< "${foo:0:1}"
H

Reversal using functions in Bash

I am trying to create a reverse function in Bash where it will reverse either a directory or just an array of items as a parameter using a main function and a reverse function. I believe that my main function is messed up or that I cannot invoke the reverse function that I have created. I am not that familiar with Bash either.
#!/bin/bash
function reverse(){
input=$1 #1st parameter
copy=${input}
len=${#copy}
for((i=$len-1;i>=0;i--)); do
if [ $i = "[" ]; then
continue
fi
rev="$rev${copy:$i:1}"
done
echo "var: $var, rev: $rev"
}
function main(){
arr=( tiger lion bear )
mydir = $arr
reverse $mydir
echo $reverse
# should print: bear lion tiger
}
main
There are two big problems with your code:
When assigning to mydir, you probably want to copy the whole array. But you assign $arr, which is the same as assigning only the first element, ${arr[0]}. Quoting Arrays section of man bash:
Referencing an array variable without a subscript is equivalent to referencing with a subscript of 0.
You should use mydir=( "${arr[#]}" ) to copy the array and then a similar construct to pass the whole array to the reverse function. But you don’t have to copy the array at all, this main is sufficient:
function main(){
arr=( tiger lion bear )
reverse "${arr[#]}"
}
Your implementation of reverse reverses letters in its first parameter, i.e. the first item of arr array, as it is called now. You should reverse the array instead. It will be stored in parameters of the function, the # variable. You can get all the parameters of your function as a params array via params=( "$#" ).
The second point strongly hints me that you have no idea what you are doing and you just copy-pasted parts of the script from somewhere. I will not write the code for you. It is really easy to do if you know the basics of Bash. If you don’t, go learn them. You will learn nothing by copy-pasting ad-hoc snippets from Stack Overflow or other sites. Next time, you would come to ask virtually the same question again. We are not here to make your homework for you, we are here to teach you.
There are also several minor issues, but still pretty severe in terms of functionality.
You must not have spaces around the = in variable assignment in shell. You have them in main when assigning to mydir. This is why Bash probably prints an error saying that is cannot find any command named mydir. Each shell splits the command line into words and treats the first word as command name.
Your reverse function copies input variable needlessly – unless you really want to print the original value at the end as you do now, of course. But then, I would rather copy the content to a variable named original and work on the input variable, because then the intent would be easier to understand.
Everywhere you expand a variable in shell, you should expand it inside double quotes, unless you have a good reason to do otherwise and you know what you are doing. If you expand it outside double quotes, you are going to get into trouble with escaping errors. Double quotes protect most special characters inside from being interpreted by the shell, only allowing variable, command, arithmetic and history expansion.
In the loop in reverse, i is supposed to contain a number. It is nonsense to test if for string equality to [. That condition is always false.
You never initialize var variable, but you use its value in the echo statement at the end of reverse body.
You also do not specify value of rev variable before you first use it. As all variables are empty by default and you use reverse only once, this is not an issue, but it still is not a good practice.
Another variable without initialization is reverse at the last line of main. Function call in shell returns only status, pretty much like any other command. You can modify a (global) variable inside the function, but you don’t do that. The echo command at the end of main is thus useless.
I already told you enough to figure out the correct implementation of reverse even if you are a shell beginner. If you read the Arrays section of Bash manual again, everything should be clear. If it is not, comment and I’ll try to give you more guidance.

extract variable's value from script file in Install Anywhere

I am using Install Anywhere 2012 and would like to be able to parse a batch or shell script for a give value and have that value stored in an IA variable. For instance, if I have the following shell file:
MY_VAR1=123
MY_VAR2=a\b\c
ECHO $MY_VAR1
I would like to pass in the path to the file and the variable name (ex. MY_VAR1) and have the result, 123, stored in an IA variable of my choosing (lets say $OUTPUT$). I could achieve this through writing some java custom code but was wondering if there was an alternative approach built into IA that would make this much easier. The variable will not be initialized when I need to figure out its value so essentially just echoing it's value or something similar will not work. Any help would be greatly appreciated!
example in Windows batch:
#echo off &setlocal
for /f "tokens=2delims==" %%a in ('findstr "MY_VAR1" "ShellFile"') do set "output=%%a"
if defined output (echo MY_VAR1: %output%) else echo MY_VAR1 not found!
On Linux/Unix, you could use perl or awk (both are standard utilities in most distros). Python or Ruby are also candidates, but may not be installed on your target system. You could even write your own targeted parser using Lex and Yacc and ship it with your installer. However, for your needs, that's surely overkill.
Here's an example of a possible awk solution in an Execute Script/Batch File Action:
#!/bin/bash
awk '
# Process lines that begin with our variable name,
# preceded by optional spaces or tabs.
/^[ \t]*$TARGET_VARIABLE_NAME$=.+/ {
# Split the current line on "=" into
# an array called result
split($0, result, "=")
value = result[1]
# Look for trailing comments and remove them.
offset = index(value, "#")
if (offset > 0) {
value = substr(value, 1, offset - 1)
}
# Remove any possible leading spaces and quotes.
# Note that the single-quote is escaped. That escape
# is for bash, not for awk. I am doing this from
# memory and do not have access to IA right now.
# you may have to play with the escaping.
gsub(/^[\'" ]*/, "", value)
# Remove any possible trailing spaces and quotes.
# See above regarding the escaped single-quote.
gsub(/[\'" ]*$/, "", value)
# send "value" to stdout
print value
}
' < $SHELL_INPUT_FILE$
The print value line (near the end) sends value to stdout.
In the Execute Script/Batch File Action settings you can designate variables that receive the stdout and stderr streams produced by the script action. By default, the stdout stream is stored in $EXECUTE_STDOUT$. You can change this to a variable name of your choosing.
In the example, above, $TARGET_VARIABLE_NAME$ and $SHELL_INPUT_FILE$ are InstallAnywhere variables that hold the name of the variable to find and the name of the file to parse, respectively. These variables will be replaced by their values before the Action executes.
Assume we have a script called /home/fred/hello.sh, which contains the following code:
#!/bin/bash
WIFE='Wilma'
NEIGHBOR="Barney Rubble"
echo "Hello to $WIFE and $NEIGHBOR from $PWD"
Before the Execute Script/Batch File Action runs, stuff the name of the script file into $SHELL_INPUT_FILE$ (/home/fred/hello.sh). Then set the value of $TARGET_VARIABLE_NAME$ to the variable you wish to find (say, NEIGHBOR). After the action completes, $EXECUTE_STDOUT$ in InstallAnywhere will contain Barney Rubble.
You can build on this idea to parse arbitrarily complex files in an Execute Script/Batch File Action. Just make your awk (or perl/Ruby/Python) script as complex as needed.
NOTE: when scripting Unix shell scripts in InstallAnywhere ALWAYS check the "Do not replace unknown variables" option. If you don't, InstallAnywhere will quietly convert anything that looks vaguely like an InstallAnywhere variable into blanks... It's very annoying.
For a Windows solution, find a standalone Windows version of awk or perl and include it with your installation. Then extend the above solution to work for batch files.
You'd want to create two Execute Script/Batch File Actions, one with a rule for Linux/Unix and one with a rule for Windows. You'd have to install the Windows awk or perl executable before calling this action though. Also, you'd need to fully qualify the path to the awk/perl executable. Finally, the actual script will need to be sensitive to differences in batch syntax versus shell syntax.
Below is an awk script modified to look for batch variable definitions. The pattern changes and you won't have to worry about embedded comments:
$PATH_TO_AWK_EXE$ '
# This pattern looks for optional spaces, the word SET
# with any capitalization, the target variable, more
# optional spaces and the equals sign.
/^[ \t]*[Ss][Ee][Tt][ \t]*$TARGET_VARIABLE_NAME$[ \t]*=.+/ {
# Split the current line on "=" into
# an array called result
split($0, result, "=")
value = result[1]
# No trailing comments in Batch files.
# Remove any possible leading spaces and quotes.
# Note that the single-quote is escaped. That escape
# is for bash, not for awk. I am doing this from
# memory and do not have access to IA right now.
# you may have to play with the escaping.
gsub(/^[\'" ]*/, "", value)
# Remove any possible trailing spaces and quotes.
# See above regarding the escaped single-quote.
gsub(/[\'" ]*$/, "", value)
# send "value" to stdout
print value
}
' < $SHELL_INPUT_FILE$
Above, the IA variable $PATH_TO_AWK_EXE$ points to the location where awk was installed. It would be set as some combination of $USER_INSTALL_FOLDER$, possibly other directory names, and the name of the awk.exe file. $PATH_TO_AWK_EXE$ can later be used to remove the awk executable, if desired.
You can try to get variable from output of script
"Execute Script" -> Store process's stdout in: $EXECUTE_OUTPUT$
Than you can use $EXECUTE_OUTPUT$ as variable after that

Expanding known env vers in a file and leaving others

I'm sure there's a really easy way of doing this. I'm trying to take a file which contains some environment variables and expand it so that those which are known are expanded to their values whereas those which are not are left alone.
For example, if my file contained the following:
${I_EXIST}
${I_ALSO_EXIST}
${I_DONT_EXIST}
this would be expanded to:
existValue
alsoExistValue
${I_DONT_EXIST}
I ideally want to do this as simply as possible so I don't want a complex substitution using sed, awk or perl. I'm thinking of something similar to a "Here" file, but apart from the fact that I can't get the syntax right, it also blanks out anything which does not expand. E.g:
cat <<EOF
> ${I_EXIST}
> ${I_ALSO_EXIST}
> ${I_DONT_EXIST}
EOF
existValue
alsoExistValue
(i.e. the last value expands to nothing)
Update
Should really have made clear that I was thinking about potentially more than one substitution per line. One way I did find to do this, if we're not fussed about the variables appearing in the file as ${MYVAR} but maybe MYVAR will do:
m4 $( env | sed 's/\([A-Za-z0-9]*\)=\([\/A-Za-z_0-9:|%*. -#]*\)/-D\1=\2' ) myfile
This uses the M4 preprocessor to substitute all the pairs in your environment. A couple of caviats here:
Sorry about the reg exp stuff. It looks pretty nasty and I'm sure there are nicer ways of expressing this. I found problems if my env vars had spaces in them or any unusual characters that weren't in the set.
Of course this is a blunt substitution tool (which I was trying to avoid) so variable might get substituted when you didn't want it to happen.
#!/bin/bash
while read a;
do
n=$(eval echo $a)
if [[ "$n" == "" ]]
then
echo $a
else
echo $n
fi
done < input
Using this as input
${HOME}
${nonexistent}
Gives
/home/myuser
${nonexistent}
Easy to read? Maybe not. It is short and works though :-)
while read r; do
echo $(eval echo ${r%\}}:-'$r'\})
done < input
Magic used:
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion
Edit: Further explanation, I hope it makes some sense.
We use two techniques; from the above docs:
${parameter:−word} If parameter is
unset or null, the expansion of word
is substituted. Otherwise, the value
of parameter is substituted.
And
${parameter%word}
The word is expanded to produce a
pattern just as in filename expansion.
If the pattern matches a trailing
portion of the expanded value of
parameter, then the result of the
expansion is the value of parameter
with the shortest matching pattern
(the ‘%’ case) or the longest matching
pattern (the ‘%%’ case) deleted. ...
We use the fact that the input is just what we can use in the shell, we have ${FOOBAR} but need ${FOOBAR:-'${FOOBAR}'} (Single quotes to avoid expansion).
# echo ${doesntexist:-Hello}
Hello
# doesexist=World
# echo ${doesexist:-Will not be printed}
World
So what we need to inject is :-'${FOOBAR}'
To achieve this we trim the } at the end, add the string, then put another } back afterwards.
# echo $r
${FOOBAR}
# echo ${r%\}}
${FOOBAR
The final \} isn't really necessary, since it's got no beginning in this case, but it's better to be explicit and escape it. (Much like you would escape echo \* even if echo * without any matching files gives you a literal *).
Edit2: This of course doesn't take into account that you wanted to support multiple variables in a single row; or any rows with other stuff in them.
while read name; do echo "$name = " $(eval echo $name); done < file_with_vars.txt
will echo all variables what know.
e.g.
in my file called vv
${PATH}
${HAVENOT}
${LOCALE}
will print
${PATH} = /usr/local/narwhal/bin:/opt/local/bin:/opt/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:~/bin
${HAVENOT} =
${LOCALE} = UTF-8
modify the output format as you wish :)

Resources