How to verify the output of a command in shell? - linux

I need to verify if a particular file system has the noexec option set.
For example /dev/shm. I am running the command in the following manner:
get_val=$(mount|grep /dev/shm )
if [[ -z get_val ]] ; then
# Error
else
value=$(echo "${get_val}" | cut -d " " -f 6 | grep noexec)
if [ "${value}" = "" ]; then
# Not set
else
# Set
fi
fi
The value of get_val is something like devshm on /dev/shm type devshm (rw,noexec,nosuid,gid=5,mode=0620)
Next what I want to do is check if gid and mode has been set to a certain value. However, with the above procedure, I can only check if an option is set.
So I tried this:
echo "${get_val}"| cut -d " " -f 6 | awk -F, '{
if ($4 == "gid=123"){
print 1;
}
else
{ print 0;}
if ($5 == "mode=123)"){
print 1;
}
else
{ print 0;}'
However, this seems too hassle-ish and I am not sure what will be the better way to do this.
Also other parameters could be set in a filesystem such as nodev etc which would make $5 or $2 different.
any suggestions?

Looks like you really should be turning to Awk even for the basic processing.
if mount | awk '$1 == "/dev/shm" && $6 ~ /(^|,)noexec(,|$)/ &&
$6 ~ /(^|,)gid=123(,|$)/ && $6 ~ /(^|,)mode=123(,|$)/ { exit 0 }
END { exit 1 }'
then
echo fine
else
echo fail
fi
The (^|,) and (,|$) anchors are to make sure the matches are bracketed either by commas or beginning/end of field, to avoid partial matches (like mode=1234).
Getting Awk to set its return code so it can be used idiomatically in an if condition is a little bit of a hassle, but a good general idea to learn.

Why not directly use globbing with [[:
[[ ${get_val} == *\(*gid=5*\)* ]] && echo 'Matched'
Similarly, with "mode=0620":
[[ ${get_val} == *\(*mode=0620*\)* ]] && echo 'Matched'
If you prefer Regex way:
[[ ${get_val} =~ \(.*gid=5.*\) ]] && echo 'Matched'
[[ ${get_val} =~ \(.*mode=0620.*\) ]] && echo 'Matched'

Sorry if this is stupid, but isn't it as simple as
mount | grep -E '^/dev/shm.*noexec' && value=1
((value)) && #Do something useful
If you wist to check multiple fields you can pipe the grep like below :
mount | grep -E '^/dev/shm.*noexec' \
| grep -E 'gid=5.*mode=0620|mode=0620.*gid=5' >/dev/null && value=1
((value==1)) && #Do something useful

Related

Unix - Replace column value inside while loop

I have comma separated (sometimes tab) text file as below:
parameters.txt:
STD,ORDER,ORDER_START.xml,/DML/SOL,Y
STD,INSTALL_BASE,INSTALL_START.xml,/DML/IB,Y
with below code I try to loop through the file and do something
while read line; do
if [[ $1 = "$(echo "$line" | cut -f 1)" ]] && [[ "$(echo "$line" | cut -f 5)" = "Y" ]] ; then
//do something...
if [[ $? -eq 0 ]] ; then
// code to replace the final flag
fi
fi
done < <text_file_path>
I wanted to update the last column of the file to N if the above operation is successful, however below approaches are not working for me:
sed 's/$f5/N/'
'$5=="Y",$5=N;{print}'
$(echo "$line" | awk '$5=N')
Update: Few considerations which need to be considered to give more clarity which i missed at first, apologies!
The parameters file may contain lines with last field flag as "N" as well.
Final flag needs to be update only if "//do something" code has successfully executed.
After looping through all lines i.e, after exiting "while loop" flags for all rows to be set to "Y"
perhaps invert the operations do processing in awk.
$ awk -v f1="$1" 'BEGIN {FS=OFS=","}
f1==$1 && $5=="Y" { // do something
$5="N"}1' file
not sure what "do something" operation is, if you need to call another command/script it's possible as well.
with bash:
(
IFS=,
while read -ra fields; do
if [[ ${fields[0]} == "$1" ]] && [[ ${fields[4]} == "Y" ]]; then
# do something
fields[4]="N"
fi
echo "${fields[*]}"
done < file | sponge file
)
I run that in a subshell so the effects of altering IFS are localized.
This uses sponge to write back to the same file. You need the moreutils package to use it, otherwise use
done < file > tmp && mv tmp file
Perhaps a bit simpler, less bash-specific
while IFS= read -r line; do
case $line in
"$1",*,Y)
# do something
line="${line%Y}N"
;;
esac
echo "$line"
done < file
To replace ,N at the end of the line($) with ,Y:
sed 's/,N$/,Y/' file

Edit conf files and change values with sed or awk in the dockerfile?

i have a bunch of conf files, which can be described as two types:
Type 1 (Typical conf-file-like ini files):
[server]
# Protocol (http, https)
;protocol = http
# The ip address to bind to, empty will bind to all interfaces
;http_addr = 192.168.33.2
# The http port to use
;http_port = 3000
Type 2 (malformated conf file):
###
### [http]
###
[http]
# Determines whether HTTP endpoint is enabled.
# enabled = true
# The bind address used by the HTTP service.
# bind-address = ":8080"
For Type1 conf files, i successfully could use tools like crudini p.a with crudini --set filename.conf server protocol https which actually adds a new entry under server section instead of uncommenting the existing. As long it works its ok.
crudini failes with Type 2 files with a parse error, because the conf file is not a proper ini file. For these i tried using sed but failed.
What i want to achieve is to use one script/tool to be able to modify both type of files. Maybe a good approach could be to:
First find the section, but ignore lines leading with ; or # and section name
search within all lines of the section for the parameter.
if the line of the parameter starts with ; or #, replace the complete line (this also gets rid of the white spaces and inserts it at same position)
if the parameter is not found, it should be added
I found a lot of scripts, with a lot of code for this, but need a small footprint solution working with Docker conf files.
Can you help me to find a elegant solution for this?
Here. Pay it forward. The leg work is handled by gawk. I tested it with the --posix switch, so I think it should work with mawk and other awk variants as well.
The script will quote values containing spaces and equal signs, and values where the value being replaced was quoted. I'm not familiar with Docker files, but since ":8000" was quoted in your second example I thought the quoting might be important.
#!/bin/bash
usage() {
echo "Usage: $(basename $0) -s section -i item -v value filename"
exit
}
export LC_ALL=C
while getopts "s:i:v:" i || (( $# )); do {
case $i in
s) section="$OPTARG";;
i) item="$OPTARG";;
v) value="$OPTARG";;
?) [[ -f $1 ]] && filename="$1";shift;;
esac
}; done
[[ -z "$section" ]] || [[ -z "$item" ]] || [[ -z "$filename" ]] && usage
[[ -w "$filename" ]] && {
tmpfile="$(mktemp -p /dev/shm)"
[[ $(whoami) = "root" ]] && chown --reference="$filename" "$tmpfile"
chmod --reference="$filename" "$tmpfile"
} || {
echo "Invalid filename: $filename"
usage
}
cleanup() {
[[ -f "$tmpfile" ]] && rm -f "$tmpfile"
exit
}
trap cleanup EXIT
awk -v section="$section" -v item="$item" -v value="$value" '
function quote(str) { return "\"" str "\"" }
/^\[[^\]]+\]/ {
if (flag) {
printf "%s = %s\n", item, value ~ /[[:space:]=]/ ? quote(value) : value
flag = 0
}
else flag = (section == substr($0, 2, index($0, "]") - 2))
}
$0 ~ ("^[[:space:]#;]*" item "[[:space:]]*=") {
if (flag) {
$0 = sprintf("%s = %s", item, /"/ || value ~ /[[:space:]=]/ ? quote(value) : value)
flag = 0
}
}
1
END { if (flag) printf "%s = %s", item, value ~ /[[:space:]=]/ ? quote(value) : value }
' "$filename" >"$tmpfile"
[[ -s "$tmpfile" ]] && mv "$tmpfile" "$filename" || echo "Something went horribly wrong."

How to use sed or awk command to set a variable based on the values of two other variables

I'm trying to write a bash script which will look for outdated entries in the conf file and update them with the new value.
The old conf file will have two entries(-CAR and -BIKE). For example,
#Following are the entries in the conf file
#-CAR yes/no default set to no
#-BIKE yes/no default set to no
-CAR yes
-BIKE no
Now this bash script should grep for those entries and replace them with a single entry as -DRIVING_LEVEL (car/bike/none).
The logic is as follows:
If -CAR is set to yes, then DRIVING_LEVEL will be "car".
If -CAR is set to no and -BIKE is set to yes, then DRIVING_LEVEL will be "bike".
If both -CAR and -BIKE are set to none, then DRIVING_LEVEL will be "none".
I have written the following script which does the exact thing.
#!/bin/bash
CONF_FILE="license.conf"
grep -w $CONF_FILE -e '-CAR' -e '-BIKE' > /dev/null
if [ $? -eq 0 ]
then
echo "Old values are present"
var=$(grep -w $CONF_FILE -e '-CAR')
if [ $? -eq 0 ]
then
temp="${var##*$'\n'}"
option=$(echo $temp | sed 's/.* //g')
echo $option
if [ "$option" == "yes" ]; then
car=1
elif [ "$option" == "no" ]; then
car=0
fi
echo "Deleting $var"
sed -i '/-CAR/d' $CONF_FILE
fi
var2=$(grep -w $CONF_FILE -e '-BIKE')
if [ $? -eq 0 ]
then
temp2="${var2##*$'\n'}"
option=$(echo $temp2 | sed 's/.* //g')
echo $option
if [ "$option" == "yes" ]; then
bike=1
#echo $temp
elif [ "$option" == "no" ]; then
bike=0
fi
echo "Deleting $var2"
sed -i '/-BIKE/d' $CONF_FILE
fi
if [ "$car" == "1" ]; then
driving_level="car"
elif [ "$bike" == "1" ]; then
driving_level="bike"
else
driving_level="none"
fi
echo "Appending -DRIVING_LEVEL $driving_level"
echo "-DRIVING_LEVEL $driving_level" >> $CONF_FILE
else
echo "No old values"
fi
The things is that I think this is too long and can be shortened using sed or awk commands. But I couldn't make it. I want it to be as short as possible.
Thanks in advance !
Note:
I have used temp="${var##*$'\n'}" to retrieve the last line which has the actual value because grep will give both the lines containing -BIKE.
I have used option=$(echo $temp | sed 's/.* //g') because there can be any number of spaces between -BIKE and yes/no.
$ awk '($0 ~ /-CAR.*yes$/){dl="car";exit} ($0 ~ /-BIKE.*yes$/){dl="bike"} END{print "-DRIVING_LEVEL",(dl)?dl:"none"}' license.conf
-DRIVING_LEVEL car
Brief explanation,
$0 ~ /-CAR.*yes$/: if "-CAR yes" is found, print the result directly (that's the reason I use exit here)
($0 ~ /-BIKE.*yes$/): if "-BIKE yes" is found, keep finding the follow up lines since there's possibility to find "CAR".
Print the result as "none" if nothing is found.
Your whole script can just be:
awk '1; /^-CAR/ && $2 == "yes" {DL="car"} /^-BIKE/ && $2 == "yes" {DL="bike"}
END{print "-DRIVING_LEVEL", DL}' DL=none license.conf
This just appends a value for DRIVING_LEVEL at the end (note that if both car and bike are "yes", this will select the last one in the file, but you didn't specify what behavior you want in that case.) If you want to suppress the output of the original values:
awk '/^-CAR/ && $2 == "yes" {DL="car"} /^-BIKE/ && $2 == "yes" {DL="bike"}
! (/^-CAR/ || /^-BIKE/)
END{print "-DRIVING_LEVEL", DL}' DL=none license.conf
To check for the existence of the original entries, you can do:
awk '/^-CAR/{DL=($2 == "yes" ? "car" : "none"); next}
/^-BIKE/{ if(DL== "unset" || DL == "none"){DL=($2 == "yes" ?"bike":"none")} next}
1
END{
if(DL=="unset") { print "missing values" > "/dev/stderr"; exit 1 }
else
print "-DRIVING_LEVEL", DL}' DL=unset license.conf
Although if entries are missing this will output the file before finally noticing and emitting the error message. Probably cleaner to pre-check the input.

How to change/overwrite certain characters in a string?

I've written up a 'hang-man' type game in bash. Which is currently working, but I just cannot get my head around on how to overwrite certain characters in the string.
So currently I get a letter (eg 'l') from the user and it checks it against a string 'hello' and prints out as ' _ _ l l _ ' ({is $prev in the code) I would like when the user enters 'e', the $prev to get updated to ' _ e l l _ ' and so on and so forth for 'h' and 'o'.
Q: How would I be able to change a certain character in a string?
If you want to manipulate individual characters in a variable, then it is easier to hold all characters in an array. That way you can change the characters based on array index. Below shows a simple way to convert to/from an array while manipulating the characters. Note: there is no need to convert back to a variable, you can simply maintain the word being guessed at in an array:
#!/bin/bash
declare -a array
prev="__||_" ## previous state of guessing
printf "%s\n" "$prev"
for ((i=0; i<${#prev}; i++)); do ## convert to array
array+=( ${prev:i:1} )
done
array[1]="e" ## update letter
## convert back to string
printf -v prev $(echo ${array[#]} | tr -d ' ')
printf "%s\n" "$prev"
Output
$ bash ~/scr/tmp/stack/strtoarray.sh
__||_
_e||_
An implementation of hangman using arrays for the word and the answer. It picks a random word from the file pointed to by ${words}.
#!/usr/bin/env bash
words='/usr/share/dict/words'
die() { >&2 echo $#; exit 1; }
join() { local IFS="$1"; shift; echo "$*"; }
new_word() {
unset word
local length=$(wc -l < ${words})
local count=$((${RANDOM} % ${length}))
head -${count} ${words} | tail -1 | tr '[:upper:]' '[:lower:]'
}
draw_hangman() {
# draw hangman here
((++guesses))
}
[[ -f ${words} ]] || die "Missing words file: ${words}"
declare -i guesses=0
declare -a word=( $(new_word | grep -o .) )
declare -a answer=( $(printf '_ %.0s' $(seq 1 ${#word[#]})) )
while :; do
echo -e "\nPress - to quit.\n"
echo -e "${answer[#]}\n"
read -s -n 1 -p "$((${#word[#]} - ${guesses})) more guesses: " guess
echo
[[ ${guess} == '-' ]] && echo && break # quit
[[ ${guesses} -eq $((${#word[#]} - 1)) ]] && echo -e "\nYOU LOSE!\n" && \
echo "The word was $(join '' ${word[#]})" && break
[[ ${word[#]} != *${guess}* ]] && draw_hangman && continue
for i in ${!word[#]}; do
[[ ${word[${i}]} == ${guess} ]] && answer[${i}]=${guess}
done
[[ ${answer[#]} == ${word[#]} ]] && echo -e "\nYOU WIN!\n" && break
done

Bash Comparing strings in a directory

Hi I am trying to compare two strings in a directory. the format is as follows.
{sametext}difference{sametext}.
Note: {sametext} is not static for each file
for example
myfile_1_exercise.txt compared to myfile_2_exercise.txt
Can you tell me how I would match the above strings in an if statement.
Basically I need to know how I would ignore the number in the two strings so that these would be same.
Some example code is shown below:
My example code looks like this:
for g in `ls -d */`;
do
if [ -d $g ]; then
cd $g # down 1 directories
for h in `ls *root`;
do
printf "${Process[${count}]} = ${basedir}/${f}${g}${h}\n"
h1=${h}
if [ "${h1}" = "${h2}" ]; then # NEED to MATCH SOME HOW??????????
echo we have a match
fi
h2=${h1}
let count+=1
done
cd ../
#printf "\n\n\n\n"
fi
done
What should be the test to determine this instead of "${h1}" = "${h2}"?
Cheers,
Mike
"myfile_1_exercise.txt" == "myfile_2_exercise.txt"
You mean the above test should return true (ignoring the numbers) right?
This is what I would have done:
h1="myfile_1_exercise.txt"
h2="myfile_2_exercise.txt"
if [ $( echo ${h1} | sed 's/[0-9]*//g' ) == $( echo ${h2} | sed 's/[0-9]*//g' ) ] ; then
# do something here.
fi
sed comes in handy here.
This basically goes through every file in the directory, extracts the two strings from the filename, and keeps a list of all the unique combinations thereof.
Then, it walks through this list, and uses bash's wildcard expansion to allow you to loop over each collection.
EDIT: Got rid of an ugly hack.
i=0
for f in *_*_*.txt
do
a=`echo "$f" | sed 's/\(.*\)_.*_\(.*\).txt/\1/g'`
b=`echo "$f" | sed 's/\(.*\)_.*_\(.*\).txt/\2/g'`
tmp=${all[#]}
expr match "$tmp" ".*$a:$b.*" >/dev/null
if [ "$?" == "1" ]
then
all[i]="$a:$b"
let i+=1
fi
done
for f in ${all[#]}
do
a=`echo "$f" | sed 's/\(.*\):\(.*\)/\1/g'`
b=`echo "$f" | sed 's/\(.*\):\(.*\)/\2/g'`
echo $a - $b
for f2 in $a_*_$b.txt
do
echo " $f2"
# ...
done
done
Of course, this assumes that all the files you care about follow the *_*_*.txt pattern.
Disclaimer:
Mileage can vary and you may have to adjust and debug the script for corner cases.
You may be better off using Perl for your task.
There could be better solutions even in Bash. This one is not very efficient but it seems to work.
Said that, here is a script that compares two strings according to your requirements. I am sure you can figure how to use it in your directory listing script (for which you may want to consider find by the way)
This script takes two strings and prints match! if they match
$ bash compare.sh myfile_1_exercise.txt myfile_2_exercise.txt
match!
$ bash compare.sh myfile_1_exercise.txt otherfile_2_exercise.txt
$
The script:
#!/bin/bash
fname1=$1
fname2=$2
findStartMatch() {
match=""
rest1=$1 ;
rest2=$2 ;
char1=""
char2=""
while [[ "$rest1" != "" && "$rest2" != "" && "$char1" == "$char2" ]] ; do
char1=$(echo $rest1 | sed 's/\(.\).*/\1/');
rest1=$(echo $rest1 | sed 's/.\(.*\)/\1/') ;
char2=$(echo $rest2 | sed 's/\(.\).*/\1/');
rest2=$(echo $rest2 | sed 's/.\(.*\)/\1/') ;
if [[ "$char1" == "$char2" ]] ; then
match="${match}${char1}"
fi
done
}
findEndMatch() {
match=""
rest1=$1 ;
rest2=$2 ;
char1=""
char2=""
while [[ "$rest1" != "" && "$rest2" != "" && "$char1" == "$char2" ]] ; do
char1=$(echo $rest1 | sed 's/.*\(.\)/\1/');
rest1=$(echo $rest1 | sed 's/\(.*\)./\1/') ;
char2=$(echo $rest2 | sed 's/.*\(.\)/\1/');
rest2=$(echo $rest2 | sed 's/\(.*\)./\1/') ;
if [[ "$char1" == "$char2" ]] ; then
match="${char1}${match}"
fi
done
}
findStartMatch $fname1 $fname2
startMatch=$match
findEndMatch $fname1 $fname2
endMatch=$match
if [[ "$startMatch" != "" && "$endMatch" != "" ]] ; then
echo "match!"
fi
If you are actually comparing two files like you mentiond ... probably you can use diff command like
diff myfile_1_exercise.txt myfile_2_exercise.txt

Resources