Strange Behavior in Bash For Loop - bash

Given the following code, BASH's output is unexpected for me and I'm looking for possible solutions (I've tried changing the way I'm quoting but that doesn't seem to affect anything to produce the desired result):
Testing File:
#!/bin/bash
. FindMissingSettings.function
Settings[0]="FirstSetting"
Settings[1]="SecondSetting"
Settings[2]="ThirdSetting"
ThisFile="ThisFile"
find_missing_settings "${Settings[#]}" "$ThisFile"
The included FindMissingSettings.function:
#!/bin/bash
find_missing_settings () {
Settings=("$#")
File=$2
for Setting in "${Settings[#]}"; do
echo "$Setting"
echo "1"
done
echo "$File"
echo "2"
}
I expected the output from this script and the included function to be:
FirstSetting
1
SecondSetting
1
ThirdSetting
1
ThisFile
2
However this was the result I received:
FirstSetting
1
SecondSetting
1
ThirdSetting
1
ThisFile
1
SecondSetting
2
Why is this and what can I do to provide the desired result? Thank you!

In your find_missing_settings function, in your variable Setting, you have all the given inputs (FirstSetting, Second Setting, ThirdSetting, ThisFile). That's why it print it all with during the loop.
Then it print the 2nd setting in the list, so SecondSetting
To fix this, you can put ThisFile as first parameter of the function:
find_missing_settings "$ThisFile" "${Settings[#]}"
And change in the find_missing_settings function how you get the inputs:
Settings=("${#:2}")
File=$1
The :2 ask to get the inputs starting from the 2nd one only, and you put the first one (ThisFile) in the variable File

Related

Change name of Variable while in a loop

I have this idea in mind:
I have this number: CN=20
and a list=( "xa1-" "xa2-" "xb1-" "xb2-")
and this is my script:
for a in "${list[#]}"; do
let "CN=$(($CN+1))"
echo $CN
Output:
21
22
23
24
I am trying to create a loop where it creates the following variables, which will be referenced later in my script:
fxp0_$CN="fxp-$a$CN"
fxp0_21="fxp-xa1-21"
fxp0_22="fxp-xa2-22"
fxp0_23="fxp-xb1-23"
fxp0_24="fxp-xb2-24"
However, I have not been able to find a way to change the variable name within my loop. Instead, I was trying myself and I got this error when trying to change the variable name:
scripts/srx_file_check.sh: line 317: fxp0_21=fxp0-xa2-21: command not found
After playing around I found the solution!
for a in "${list[#]}"; do
let "CN=$(($CN+1))"
fxp_int="fxp0-$a$CN"
eval "fxp0_$CN=${fxp_int}"
done
echo $fxp0_21
echo $fxp0_22
echo $fxp0_23
echo $fxp0_24
echo $fxp0_25
echo $fxp0_26
echo $fxp0_27
echo $fxp0_28
Output:
fxp0-xa1-21
fxp0-xa2-22
fxp0-xb1-23
fxp0-xb2-24
fxp0-xc1-25
fxp0-xc2-26
fxp0-xd1-27
fxp0-xd2-28
One common method for maintaining a dynamically generated set of variables is via arrays.
When the variable names vary in spelling an associative array comes in handy whereby the variable 'name' acts as the array index.
In this case since the only thing changing in the variable names is a number we can use a normal (numerically indexed) array, eg:
CN=20
list=("xa1-" "xa2-" "xb1-" "xb2-")
declare -a fxp0=()
for a in "${list[#]}"
do
(( CN++ ))
fxp0[${CN}]="fxp-${a}${CN}"
done
This generates:
$ declare -p fxp0
declare -a fxp0=([21]="fxp-xa1-21" [22]="fxp-xa2-22" [23]="fxp-xb1-23" [24]="fxp-xb2-24")
$ for i in "${!fxp0[#]}"; do echo "fxp0[$i] = ${fxp0[$i]}"; done
fxp0[21] = fxp-xa1-21
fxp0[22] = fxp-xa2-22
fxp0[23] = fxp-xb1-23
fxp0[24] = fxp-xb2-24
As a general rule can I tell you that it's not a good idea to modify names of variables within loops.
There is, however, a way to do something like that, using the source command, as explained in this URL with some examples. It comes down to the fact that you treat a file as a piece of source code.
Good luck

less: filter out pattern passed as command line argument + follow file via bash function

I'm trying to create a bash function that will use less to apply a pattern and follow the file using the argument passed to the function
my_less_function() {
if [ -z "$1" ]
then
# if no arg
less +F /var/log/my.log
else
# else, filter out the arg
less +$'&!'$1'\nF' /var/log/my.log
fi
}
my issue is that i can't get the arg to substitute properly in the else block
my_less_function MY_VALUE displays Non-match &/MY_VALUE\nF in less
it looks like it's concatenating the argument and \nF, but \nF is supposed to trigger the follow command instead of being interpreted as part of the argument
any ideas?
wrong : less +$'&!'$1'\nF' /var/log/my.log
right : less +$'&!'${1}$'\nF' /var/log/my.log

Comparing result, input and output of tests in shell

Basically what I want to do is write a shellscript that will run series of test, take in any input (if there is any) and compare the test result with an output.
So my file looks something like this.
#!/bin/sh
# Let's set our working directory
DIR="./tests"
# And create some variables
passed=0
failed=0
totalamount=0
returned=0
# Inform the user about start of testing
echo "========= TESTING INITIALIZED ========="
# Actual testing I'm trying to do
So I assume first thing I should do is create a loop that will run through the tests
for file in "./tests/1/*.test do
So Now I'm unsure how can I compare the output of my .test file with a prepared .output program.
For example, I have a program test1.test, that will calculate 2+3. The result is 5. What I want is to have this value stored and compare it with my test1.output
Any idea how to do this?
In the end I'll just compare the two values
if [ $returned -eq $expected ]; then
echo "Test was succesful"
else
echo "Test was unsuccesful"
fi
Basically the end result should look something like:
Test: test1.test
Expected result: X
Your result: Y
Test was succesful/unsuccesful
You could use diff to compare the test result with the output expected. If they match, then you could assume the test was successful. For example:
test1check="$(diff /folder/test1 /folder/test1-expected)"
if [ -z "$test1check" ]; then
echo "Test was succesful"
else
echo "Test was unsuccesful, difference on results: $test1check"
fi
If the user is going to make inputs, or in other cases at your discretion, you could run the same comparison but ignore case differences (ex: "APPLE" would match "apple"), by changing the first line like this:
test1check="$(diff -i /folder/test1 /folder/test1-expected)"
One more thing: keep in mind that this code is sensitive to newlines, so if the test result does not end in a new line and the expected result does, the script will point out the difference.

Detect empty command

Consider this PS1
PS1='\n${_:+$? }$ '
Here is the result of a few commands
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
1 $
The first line shows no status as expected, and the next two lines show the
correct exit code. However on line 3 only Enter was pressed, so I would like the
status to go away, like line 1. How can I do this?
Here's a funny, very simple possibility: it uses the \# escape sequence of PS1 together with parameter expansions (and the way Bash expands its prompt).
The escape sequence \# expands to the command number of the command to be executed. This is incremented each time a command has actually been executed. Try it:
$ PS1='\# $ '
2 $ echo hello
hello
3 $ # this is a comment
3 $
3 $ echo hello
hello
4 $
Now, each time a prompt is to be displayed, Bash first expands the escape sequences found in PS1, then (provided the shell option promptvars is set, which is the default), this string is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal.
The trick is then to have an array that will have the k-th field set (to the empty string) whenever the (k-1)-th command is executed. Then, using appropriate parameter expansions, we'll be able to detect when these fields are set and to display the return code of the previous command if the field isn't set. If you want to call this array __cmdnbary, just do:
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
Look:
$ PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
0 $ [ 2 = 3 ]
1 $
$ # it seems that it works
$ echo "it works"
it works
0 $
To qualify for the shortest answer challenge:
PS1='\n${a[\#]-$? }${a[\#]=}$ '
that's 31 characters.
Don't use this, of course, as a is a too trivial name; also, \$ might be better than $.
Seems you don't like that the initial prompt is 0 $; you can very easily modify this by initializing the array __cmdnbary appropriately: you'll put this somewhere in your configuration file:
__cmdnbary=( '' '' ) # Initialize the field 1!
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
Got some time to play around this weekend. Looking at my earlier answer (not-good) and other answers I think this may be probably the smallest answer.
Place these lines at the end of your ~/.bash_profile:
PS1='$_ret$ '
trapDbg() {
local c="$BASH_COMMAND"
[[ "$c" != "pc" ]] && export _cmd="$c"
}
pc() {
local r=$?
trap "" DEBUG
[[ -n "$_cmd" ]] && _ret="$r " || _ret=""
export _ret
export _cmd=
trap 'trapDbg' DEBUG
}
export PROMPT_COMMAND=pc
trap 'trapDbg' DEBUG
Then open a new terminal and note this desired behavior on BASH prompt:
$ uname
Darwin
0 $
$
$
$ date
Sun Dec 14 05:59:03 EST 2014
0 $
$
$ [ 1 = 2 ]
1 $
$
$ ls 123
ls: cannot access 123: No such file or directory
2 $
$
Explanation:
This is based on trap 'handler' DEBUG and PROMPT_COMMAND hooks.
PS1 is using a variable _ret i.e. PS1='$_ret$ '.
trap command runs only when a command is executed but PROMPT_COMMAND is run even when an empty enter is pressed.
trap command sets a variable _cmd to the actually executed command using BASH internal var BASH_COMMAND.
PROMPT_COMMAND hook sets _ret to "$? " if _cmd is non-empty otherwise sets _ret to "". Finally it resets _cmd var to empty state.
The variable HISTCMD is updated every time a new command is executed. Unfortunately, the value is masked during the execution of PROMPT_COMMAND (I suppose for reasons related to not having history messed up with things which happen in the prompt command). The workaround I came up with is kind of messy, but it seems to work in my limited testing.
# This only works if the prompt has a prefix
# which is displayed before the status code field.
# Fortunately, in this case, there is one.
# Maybe use a no-op prefix in the worst case (!)
PS1_base=$'\n'
# Functions for PROMPT_COMMAND
PS1_update_HISTCMD () {
# If HISTCONTROL contains "ignoredups" or "ignoreboth", this breaks.
# We should not change it programmatically
# (think principle of least astonishment etc)
# but we can always gripe.
case :$HISTCONTROL: in
*:ignoredups:* | *:ignoreboth:* )
echo "PS1_update_HISTCMD(): HISTCONTROL contains 'ignoredups' or 'ignoreboth'" >&2
echo "PS1_update_HISTCMD(): Warning: Please remove this setting." >&2 ;;
esac
# PS1_HISTCMD needs to contain the old value of PS1_HISTCMD2 (a copy of HISTCMD)
PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}
# PS1_HISTCMD2 needs to be unset for the next prompt to trigger properly
unset PS1_HISTCMD2
}
PROMPT_COMMAND=PS1_update_HISTCMD
# Finally, the actual prompt:
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
The logic in the prompt is roughly as follows:
${PS1_base#foo...}
This displays the prefix. The stuff in #... is useful only for its side effects. We want to do some variable manipulation without having the values of the variables display, so we hide them in a string substitution. (This will display odd and possibly spectacular things if the value of PS1_base ever happens to begin with foo followed by the current command history index.)
${PS1_HISTCMD2:=...}
This assigns a value to PS1_HISTCMD2 (if it is unset, which we have made sure it is). The substitution would nominally also expand to the new value, but we have hidden it in a ${var#subst} as explained above.
${HISTCMD%$PS1_HISTCMD}
We assign either the value of HISTCMD (when a new entry in the command history is being made, i.e. we are executing a new command) or an empty string (when the command is empty) to PS1_HISTCMD2. This works by trimming off the value HISTCMD any match on PS1_HISTCMD (using the ${var%subst} suffix replacement syntax).
${_:+...}
This is from the question. It will expand to ... something if the value of $_ is set and nonempty (which it is when a command is being executed, but not e.g. if we are performing a variable assignment). The "something" should be the status code (and a space, for legibility) if PS1_HISTCMD2 is nonempty.
${PS1_HISTCMD2:+$? }
There.
'$ '
This is just the actual prompt suffix, as in the original question.
So the key parts are the variables PS1_HISTCMD which remembers the previous value of HISTCMD, and the variable PS1_HISTCMD2 which captures the value of HISTCMD so it can be accessed from within PROMPT_COMMAND, but needs to be unset in the PROMPT_COMMAND so that the ${PS1_HISTCMD2:=...} assignment will fire again the next time the prompt is displayed.
I fiddled for a bit with trying to hide the output from ${PS1_HISTCMD2:=...} but then realized that there is in fact something we want to display anyhow, so just piggyback on that. You can't have a completely empty PS1_base because the shell apparently notices, and does not even attempt to perform a substitution when there is no value; but perhaps you can come up with a dummy value (a no-op escape sequence, perhaps?) if you have nothing else you want to display. Or maybe this could be refactored to run with a suffix instead; but that is probably going to be trickier still.
In response to Anubhava's "smallest answer" challenge, here is the code without comments or error checking.
PS1_base=$'\n'
PS1_update_HISTCMD () { PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}; unset PS1_HISTCMD2; }
PROMPT_COMMAND=PS1_update_HISTCMD
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
This is probably not the best way to do this, but it seems to be working
function pc {
foo=$_
fc -l > /tmp/new
if cmp -s /tmp/{new,old} || test -z "$foo"
then
PS1='\n$ '
else
PS1='\n$? $ '
fi
cp /tmp/{new,old}
}
PROMPT_COMMAND=pc
Result
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
$
I need to use great script bash-preexec.sh.
Although I don't like external dependencies, this was the only thing to help me avoid to have 1 in $? after just pressing enter without running any command.
This goes to your ~/.bashrc:
__prompt_command() {
local exit="$?"
PS1='\u#\h: \w \$ '
[ -n "$LASTCMD" -a "$exit" != "0" ] && PS1='['${red}$exit$clear"] $PS1"
}
PROMPT_COMMAND=__prompt_command
[-f ~/.bash-preexec.sh ] && . ~/.bash-preexec.sh
preexec() { LASTCMD="$1"; }
UPDATE: later I was able to find a solution without dependency on .bash-preexec.sh.

Cannot evaluate script arguments from function

I've started writing shell scripts again and I've found myself in a situation where I frequently have to write debug echo's to trace what the script is doing. The, easy, way I used to do this right was to write something like this :
#!/bin/bash
myVar = 'Erractic Nonesense'
echo "myVar: $myVar"
==> myVar: Erractic Nonesense
This worked great and was fairly simple but, having to write this for every variable I wished to trace was tiring and as a person who thinks that having less code to do more stuff is great, I wrote myself a function:
#!/bin/bash
dbg() # $msg
{
echo "$#: ${!#}"
}
myVar = 'Erractic Nonesense'
dbg myVar
==> myVar: Erractic Nonesense
This works great for regular variables but, for the scripts arguments ($1, $2, etc.), does not work. Why?
==> $ ./myScript 123
#!/bin/bash
...
dbg 1 # This is the bugger in question.
==> 1: 1
And also, how can this be circumvented?
EDIT
Thanks to Barmar I now see why it behaves this way but, the second question remains.
EDIT 2
Using koodawg idea, this is the result. It works. Updated, see EDIT 4
EDIT 3
I think that a mix of EDIT 2 and set +-x will be a viable solution.
EDIT 4
Updated the logic to fall on arguments as the previous one did not always worked. Added fancy tabs.
RX_INTEGER='^[0-9]+$'
DBG_SCRIPT_ARGS=( "$0" "$#" )
DBG_PADDING=" " # tabs of 8 spaces
dbg() # $msg | OUT$args OUT$res
{
args=$#
[[ $args =~ $RX_INTEGER ]] && res="${DBG_SCRIPT_ARGS[args]}" || res="${!#}"
printf "%s%s\`%s\`\n" "$args:" "${DBG_PADDING:$(((${#args}-1)%${#DBG_PADDING}))}"
}
You would have to call the function and explicitly pass the script args to it. You could do something like:
for argz in `echo $*`
do
dbg ${argz}
done

Resources