Why variable changed after pipe? - linux

The related stub is like:
tag=('*' '#')
i=0
function output()
{
ifs="$IFS"
IFS=$'\n'
for line in $#
do
echo $'\t' "${tag[$i]}" $line
done
IFS="$ifs"
echo $i
i=$((i+1))
echo $i
i=$((i%2))
echo $i
}
output a|tee README
output b
What I want to do is:
Every time execute output to output a message block, different prefix(${tag[$ind]}) can be used for distinguishing itself from context. Besides, part-message can be redirect to file.
Result of it is:
* a
0
1
1
* b
0
1
1
With the pipe |tee README, variable $i had been reset to 0.
Why it happened and can I implement the function by this train of thought?
Thanks.

It happens becase, as stated at Bash manual, each command in a pipeline is executed as a separate process (i.e., in a subshell).
In order to preserve i variable value I suggest you to enclose the two output calls into a single shell process as follow:
#!/bin/bash
tag=('*' '#')
i=0
function output()
{
ifs="$IFS"
IFS=$'\n'
for line in $#
do
echo $'\t' "${tag[$i]}" $line
done
IFS="$ifs"
echo $i
i=$((i+1))
echo $i
i=$((i%2))
echo $i
}
(
output a
output b
) | tee README

Related

elements not getting appended to array inside the block

I've created a script that basically gets the status codes on the url.
I'am trying to add the status code if it's greater then 399.
The elements are getting appended to array correctly outside the for loop block but not inside of it.
(here that array is TP)
ARR=('http://google.com' 'https://example.org/olol' 'https://example.org' 'https://steamcommunity.com')
TP=()
TP+=("77")
ERROR=true
#for item in "${ARR[#]}";do curl -I $item | grep HTTP | awk '{print $2}'; sleep 3; echo $item; done;
for item in "${ARR[#]}";
do curl -I $item | grep HTTP | awk '{print $2}'| { read message;
echo "hi $message";
TP+=("57")
if [ $message -gt 399 ]; then
#TP+=("57");
ERROR=false;
echo "$message is greater";
fi
};
sleep 2;
echo $item;
done;
echo "${TP[#]}"
please help, I am noob.
When you pipe results (eg, from the curl call) to another command (eg, grep/awk/read) you are spawning a subshell; any variables set within the subshell are 'lost' when the subshell exists.
One idea for fixing the current code:
for item in "${ARR[#]}";
do
read -r message < <(curl -I "$item" | awk '/HTTP/ {print $2}')
echo "hi $message";
TP+=("57")
if [ "$message" -gt 399 ]
then
#TP+=("57")
ERROR=false
echo "$message is greater"
fi
sleep 2
echo "$item"
done
Where:
while the curl|awk is invoked as a subshell the result is fed to the read in the current/parent shell, so the contents of the message variable are available within the scope of the current/parent shell
added double quotes around a few variable references as good practice (eg, in case variable contains white space, in case variable is empty)

How to make "dictionary" with shell functions?

This is my code:
#!/bin/sh
echo "ARGUMENTS COUNT : " $#
echo "ARGUMENTS LIST : " $*
dictionary=`awk '{ print $1 }'`
function()
{
for i in dictionary
do
for j in $*
do
if [ $j = $i ]
then
;
else
append
fi
done
done
}
append()
{
ls $j > dictionary1.txt
}
function
I need using unix shell functions make "dictionary". For example: I write in arguments default word, example hello. Then my function checks the file dictionary1 if that word is existing in the file. If not - append that word in file, if it's already exist - do nothing.
For some reason, my script does not work. When I start my script, it waits for something and that's it.
What I am doing wrong? How can I fix it?
An implementation that tries to care about both performance and correctness might look like:
#!/usr/bin/env bash
# ^^^^- NOT sh; sh does not support [[ ]] or <(...)
addWords() {
local tempFile dictFile
tempFile=$(mktemp dictFile.XXXXXX) || return
dictFile=$1; shift
[[ -e "$dictFile" ]] || touch "$dictFile" || return
sort -um "$dictFile" <(printf '%s\n' "$#" | sort -u) >"$tempFile"
mv -- "$tempFile" "$dictFile"
}
addWords myDict beta charlie delta alpha
addWords myDict charlie zulu
cat myDict
...has a final dictionary state of:
alpha
beta
charlie
delta
zulu
...and it rereads the input file only once for each addWords call (no matter how many words are being added!), not once per word to add.
Don't name a function "function".
Don't read in and walk through the whole file - all you need is to know it the word is there or not. grep does that.
ls lists files. You want to send a word to the file, not a filename. use echo or printf.
sh isn't bash. Use bash unless there's a clear reason not to, and the only reason is because it isn't available.
Try this:
#! /bin/env bash
checkWord() {
grep -qm 1 "$1" dictionary1.txt ||
echo "$1" >> dictionary1.txt
}
for wd
do checkWord "$wd"
done
If that works, you can add more structure and error checking.
You can remove your dictionary=awk... line (as mentioned it's blocking waiting for input) and simply grep your dictionary file for each argument, something like the below :
for i in "$#"
do
if ! grep -qow "$i" dictionary1.txt
then
echo "$i" >> dictionary1.txt
fi
done
With any awk in any shell on any UNIX box:
awk -v words="$*" '
BEGIN {
while ( (getline word < "dictionary1.txt") > 0 ) {
dict[word]++
}
close("dictionary1.txt")
split(words,tmp)
for (i in tmp) {
word = tmp[i]
if ( !dict[word]++ ) {
newWords = newWords word ORS
}
}
printf "%s", newWords >> "dictionary1.txt"
exit
}'

Add a special value to a variable

I want to add a special value to the value of a variable. this is my script:
x 55;
y 106;
now I want to change the value of x from 55 to 60.
Generally, how can we apply a math expression on the values of variables in a script?
Others might come up with something simpler (ex: sed, awk, ...), but this quick and dirty script works. It assumes your input file is exactly like you posted:
this is my script.
x 55;
y 106;
And the code:
#!/bin/bash
#
if [ $# -ne 1 ]
then
echo "ERROR: usage $0 <file>"
exit 1
else
inputfile=$1
if [ ! -f $inputfile ]
then
echo "ERROR: could not find $inputfile"
exit 1
fi
fi
tempfile="/tmp/tempfile.$$"
>$tempfile
while read line
do
firstelement=$(echo $line | awk '{print $1}')
if [ "$firstelement" == 'x' ]
then
secondelement=$(echo $line | awk '{print $2}' | cut -d';' -f1)
(( secondelement = secondelement + 5 ))
echo "$firstelement $secondelement;" >>$tempfile
else
echo "$line" >>$tempfile
fi
done <$inputfile
mv $tempfile $inputfile
So it reads the input file line per line. If the line starts with variable x, it takes the number that follows, does +5 to it and outputs it to a temp file. If the line does not start with x, it outputs the line, unchanged, to the temp file. Lastly the temp file overwrite the input file.
Copy this code in a file, make it executable and run it with the input file as an argument.

erreur commande cut script shell

while i want to execute this script, the execution was blocked at the cut command and the man cut was displayed
the script code
#!/bin/bash
for i in `cat newcontext` ;do
var1=`cut –f1 –d" " $i`
var2=`cut –f2 –d" " $i`
if [ $var2 = false ];then
for j in `cat adaptrules`;do
c=`cut -f1 -d" " $j`
cc=`cut -f2 -d" " $j`
if [ $c = $var1 ];then
r=$cc
fi
done
sed /$var1/d currentconfig>>newconfig
else
for k in `cat adaptrules`;do
var3=`cut –f1 –d" " $k`
var4=`cut –f2 –d" " $k`
if [ $var3 = $var1 ];then
action=$var4
fi
done
cat $action >> newconfig
fi
done
It's difficult to know if you are trying to read from a file named in the variables i, j, and k, or if you are trying to just parse the lines of newcontext and adaptrules. In either case you should simply not use cut at all. If you are attempting the latter, you can instead do something like:
#!/bin/bash
while read var1 var2 ; do
if test "$var2" = false; then
while read c cc ; do
if test "$c" = "$var1"; then
r=$cc
fi
done < adaptrules
<&- sed /$var1/d currentconfig>>newconfig #WTF: multiple iterations with only one line omitted?
else
while read var3 var4 ; do
if test "$var3" = "$var1"; then
action=$var4
fi
done < adaptrules
<&- cat $action >> newconfig # Does $action really name a file?
# I'm guessing this should be 'echo'
fi
done < newcontext
I find the formatting of the code in the question makes it difficult to read, so I will not spend a lot of time verifying that this logic matches yours. It appears that the variable r is totally unused, the sed and the cat seem incorrect...but this gives you the idea.
Also, it might be stylistically cleaner to write the inner portion as:
if test "$var2" = false; then
while read c cc; do
...
done
else
while read var3 var4; do
...
done
...
fi < adaptrules
Note that you need to be careful that none of the commands inside the outer while loop consume anything from stdin. (hence the <&- which closes that file descriptor for sed and cat, which is not strictly necessary in these cases but demonstrates how to ensure that you do not inadvertently consume from the input to the loop.) It's probably cleaner to do things like:
while read -u 5 var1 var2; do
...
done 5< newcontext
the content of text files
my script generates a new configuration (newconfig) using the context data (newcontext), the current configuration and adaptations rules.
You would need to execute the cut command with example:
var1=$(cut -f1 -d " " <<< $i)
You are trying to execute the cut command as if $i contains a filename when it actually contains text.
my script is intended to generate a new configuration (newconfig). this configuration is generated using the context data (new context), adaptations rules (adaptrules) and the current configuration (currentconfig). the script works as follow: for each contextdata (the field var1 in newcontext), we look for its action (the field cc in adaptrules). then we verify if this action is present in the currentconfiguration. then, if the action of the selected contextdata is present in the currentconfig and the field var2 of contextdata is equal to false, then the action is deleted from the currentconfig else if the action is absent in the currentconfig and the var2 of contextdata is equal to true, so the action is added to the currentconfig. I had modified my script but it generated an error "sed, command c expects followed by text".
the new code is as follows
#!/bin/bash
while read var1 var2 ;do
while read c cc;do
if test "$c" = "$var1" ;then
r=$cc
fi
done <adaptrules
exist=false
while read var; do
if test "$var" = "$r";then
exist=true
fi
done < currentconfig
if test "$var2" = false && test "$exist" = true; then
sed -i "/$r/d" currentconfig
fi
if test "$var2" = true && test "$exist" = false; then
echo "$r">> currentconfig
fi
done < newcontext
thank you

looking for a command to tentatively execute a command based on criteria

I am looking for a command (or way of doing) the following:
echo -n 6 | doif -criteria "isgreaterthan 4" -command 'do some stuff'
The echo part would obviously come from a more complicated string of bash commands. Essentially I am taking a piece of text from each line of a file and if it appears in another set of files more than x (say 100) then it will be appended to another file.
Is there a way to perform such trickery with awk somehow? Or is there another command.. I'm hoping that there is some sort of xargs style command to do this in the sense that the -I% portion would be the value with which to check the criteria and whatever follows would be the command to execute.
Thanks for thy insight.
It's possible, though I don't see the reason why you would do that...
function doif
{
read val1
op=$1
val2="$2"
shift 2
if [ $val1 $op "$val2" ]; then
"$#"
fi
}
echo -n 6 | doif -gt 3 ls /
if test 6 -gt 4; then
# do some stuff
fi
or
if test $( echo 6 ) -gt 4; then : ;fi
or
output=$( some cmds that generate text)
# this will be an error if $output is ill-formed
if test "$output" -gt 4; then : ; fi

Resources