Bash - creating a variable out of the printed ls - bash

Im struggling with creating a bash script similiar to ps.
I just can't get it how bash works and how to make it work. Worst thing, it's first language that I don't even know how to start with and the pressure from college is making only things worse.
My plan is:
Create a while loop
Trough all the stuff of "ls /proc"
Create a variable out of found folder like "folder=$(awk '{print $i}')"
Printf the created variable and some stuff about if from /proc/i/status
End while loop if something happend to be not an integer.
And I just can't get it. Im facing tons of problems and googling them hard as usually when I start to learn a new language isn't helping to solve anything. I feel helpless with this task. I don't even know if my approuch to the problem is right :(

You don't need or want to parse ls output. Just loop over a wildcard directly.
for proc in /proc/*; do
pid=${proc#/proc/}
case $pid in *[!0-9]*) break;; esac
awk -v pid="pid" '{ printf "%i %s\n", pid, "stuff" }' $proc/status
done
You might want continue instead of break but the shell will alphabetize the hits when expanding the wildcard so the numeric entries will actually be grouped before other entries.
The parameter expansion ${variable#pattern} produces the value of variable with any leading match on pattern trimmed off. There is also ${variable%suffix} to trim a suffix matching a pattern, and a number of other simple string manipulation facilities.

You're not saying what exactly you want to extract from the process status, so let's say you want it's name and state
Borrowing from #tripleee answer, you could do:
#!/bin/sh
for proc in /proc/*
do
pid=${proc#/proc/}
case $pid in *[!0-9]*) continue;; esac
name=$(grep "Name" $proc/status | awk '{print $2$3}')
state=$(grep "State" $proc/status | awk '{print $2 " " $3}')
echo $proc $name $state | awk '{print $1 ", whose name is " $2 " is in state " $3 " " $4}'
done
Which would print something like:
/proc/98, whose name is kthrotld is in state I (idle)
/proc/9813, whose name is WebContent is in state S (sleeping)
/proc/99, whose name is acpi_thermal_pm is in state I (idle)
Or, if you want to get fancy, you could print with some color:
#!/bin/sh
for proc in /proc/*
do
pid=${proc#/proc/}
case $pid in *[!0-9]*) continue;; esac
name=$(grep "Name" $proc/status | awk '{print $2$3}')
state=$(grep "State" $proc/status | awk '{print $2 " " $3}')
echo $proc $name $state | awk '{print "\033[0;33m" $1 "\033[0;m, whose name is " "\033[0;32m" $2 "\033[0m is in state \033[0;34m" $3 " " $4 "\033[0m"}'
done
Which would output something like

Related

echo "$var" prints blank space

I was basically trying to compare two files and as part of that I assigned the cksum of the file to a variable . But when I try to compare it, it did not work. I realized that when I tried to read the variable nothing gets printed out
The below commands worked just fine
s.joseph#VA-S-JOSEPH-900 /cygdrive/c/users/Anuprita
$ test=`cksum interface2 | awk -F" " '{ print $1 }'`
s.joseph#VA-S-JOSEPH-900 /cygdrive/c/users/Anuprita
$ echo "$test"
3021988741
But when these are part of a script and I try to echo $var, nothing gets printed
$ for i in `ls interface*`;
do chksum1=`cksum $i | awk -F" " '{ print "'$1'" }'`;
echo "$chksum1";
done
s.joseph#VA-S-JOSEPH-900 /cygdrive/c/users/Anuprita
$
I am using bash shell
Without assigning it to any variable, the output is as shown below
for i in interface*; do echo "interface=\"$i\""; cksum "$i"; done
interface="interface11"
4113442291 111 interface11
interface="interface17"
1275738681 111 interface17
interface="interface2"
3021988741 186 interface2
Looks like it is an issue only with bash on cygwin. The script seems to be working just fine on unix
for i in ls interface*; do chksum1=cksum $i | awk -F" " '{ print $1 }'; echo $i, $chksum1; done
interface1, 4294967295
interface2, 4294967295
Try this;
for i in ls interface*; do echo "interface=$i"; chksum1=$(cksum $i | awk -F" " '{ print "'$1'" }'); echo "$chksum1"; done
I like adding the echo statement to verify your getting what you think with the ls statement and the variable assignment should use $(cmd) or `cmd`
Cheers
What you have in your 2nd script:
print "'$1'"
is a completely different statement from what you have in your first one:
print $1
Think about it and ask yourself why you changed it and what it is you're trying to achieve. Also man awk and see g at http://cfajohnson.com/shell/cus-faq-2.html#Q24 for what print "'$1'" does.
Best I can tell without and provided sample input your script should be written:
for i in interface*; do chksum1=$(cksum "$i" | awk '{ print $1 }'); echo "$chksum1"; done

Bash script for identifying users

I think I may have approached this problem the wrong way and I could really use a hand here.
I'm trying to print a report to the screen using awk. I want to list the users logged in and their full names next to each user. The only way I could figure it out is below, but it only shows my own info. Can I add it into a loop somehow to achieve this or did I go about it completely wrong?
This is what I have:
echo "User name" "|" "Full name"
echo "--------------------------"
echo -n "$USER " ; awk -v user="$USER" -F":" 'user==$1{print$5}' /etc/passwd
The $USER variable just contains your username.
You can pipe the who command to get the list of logged in users.
echo "User name" "|" "Full name"
echo "--------------------------"
who | while read username rest; do
echo -n "$username " ; awk -v user="$username" -F":" 'user==$1{print$5}' /etc/passwd
done
Whenever you find yourself writing a loop in shell to process text, you have the wrong solution.
who | awk '
BEGIN { print "User name|Full name\n--------------------------" }
NR==FNR { name[$1] = $5; next }
{ print $1, name[$1] }
' FS=":" /etc/passwd FS=" " -
Shell is just an environment from which to call tools and it has a language to sequence those calls. The shell tool to process text is awk.

How to add multiple line of output one by one to a variable in Bash?

This might be a very basic question but I was not able to find solution. I have a script:
If I run w | awk '{print $1}' in command line in my server I get:
f931
smk591
sc271
bx972
gaw844
mbihk988
laid640
smk59
ycc951
Now I need to use this list in my bash script one by one and manipulate some operation on them. I need to check their group and print those are in specific group. The command to check their group is id username. How can I save them or iterate through them one by one in a loop.
what I have so far is
tmp=$(w | awk '{print $1})
But it only return first record! Appreciate any help.
Populate an array with the output of the command:
$ tmp=( $(printf "a\nb\nc\n") )
$ echo "${tmp[0]}"
a
$ echo "${tmp[1]}"
b
$ echo "${tmp[2]}"
c
Replace the printf with your command (i.e. tmp=( $(w | awk '{print $1}') )) and man bash for how to work with bash arrays.
For a lengthier, more robust and complete example:
$ cat ./tstarrays.sh
# saving multi-line awk output in a bash array, one element per line
# See http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ for
# more operations you can perform on an array and its elements.
oSET="$-"; set -f # save original set flags and turn off globbing
oIFS="$IFS"; IFS=$'\n' # save original IFS and make IFS a newline
array=( $(
awk 'BEGIN{
print "the quick brown"
print " fox jumped\tover\tthe"
print "lazy dogs back "
}'
) )
IFS="$oIFS" # restore original IFS value
set +f -$oSET # restore original set flags
for (( i=0; i < ${#array[#]}; i++ ));
do
printf "array[%d] of length=%d: \"%s\"\n" "$i" "${#array[$i]}" "${array[$i]}"
done
printf -- "----------\n"
printf -- "array[#]=\n\"%s\"\n" "${array[#]}"
printf -- "----------\n"
printf -- "array[*]=\n\"%s\"\n" "${array[*]}"
.
$ ./tstarrays.sh
array[0] of length=22: "the quick brown"
array[1] of length=23: " fox jumped over the"
array[2] of length=21: "lazy dogs back "
----------
array[#]=
"the quick brown"
array[#]=
" fox jumped over the"
array[#]=
"lazy dogs back "
----------
array[*]=
"the quick brown fox jumped over the lazy dogs back "
A couple of non-obvious key points to make sure your array gets populated with exactly what your command outputs:
If your command output can contain globbing characters than you should disable globbing before the command (oSET="$-"; set -f) and re-enable it afterwards (set +f -$oSET).
If your command output can contain spaces then set IFS to a newline before the command (oIFS="$IFS"; IFS=$'\n') and set it back to it's old value after the command (IFS="$oIFS").
tmp=$(w | awk '{print $1}')
while read i
do
echo "$i"
done <<< "$tmp"
You can use a for loop, i.e.
for user in $(w | awk '{print $1}'); do echo $user; done
which in a script would look nicer as:
for user in $(w | awk '{print $1}')
do
echo $user
done
You can use the xargs command to do this:
w | awk '{print $1}' | xargs -I '{}' id '{}'
With the -I switch, xargs will take each line of its standard input separately, then construct and execute a command line by replacing the specified string '{}' in the command line template with the input line
I guess you should use who instead of w. Try this out,
who | awk '{print $1}' | xargs -n 1 id

how to prevent for loop from using space as deliminator, bash script

I am trying to right a bash script to do multiple checks and searches for a CMS my company uses. I trying to implement a function for a user to be able to search for a certain macro call and the function return all the files that contain the call, the line the macro is called on, and the actual code in the macro call. What I have seems to be getting screwed up by the fact I am using a for loop to format the output. Here's the snippet of the script I am working on:
elif [ "$choice" = "2" ]
then
echo -e "\n What macro call are we looking for $name?"
read macrocall
for i in $(grep -inR "$macrocall" $sitepath/templates/macros/); do
file=$(echo $i | cut -d\: -f1 | awk -F\/ '{ print $NF }')
line=$(echo $i | cut -d\: -f2)
calltext=$(echo $i | cut -d\: -f3-)
echo -e "\nFile: $file"
echo -e "\nLine: $line"
echo -e "\nMacro Call from file: $calltext"
done
fi
the current script runs the first few fields until it gets a a space and then everything gets all screwy. Anybody have any idea how I can have the for loops deliminator to be each result of the grep? any suggestions would be helpful. Let me know if any of you need more info. Thanks!
The right way to do this would be more like:
printf "\n What macro call are we looking for %s?" "$name"
read macrocall
# ensure globbing is off and set IFS to a newline after saving original values
oSET="$-"; set -f; oIFS="$IFS"; IFS=$'\n'
awk -v macrocall="$macrocall" '
BEGIN { lc_macrocall = "\\<" tolower(macrocall) "\\>" }
tolower($0) ~ lc_macrocall {
file=FILENAME
sub(/.*\//,"",file)
printf "\n%s\n", file
printf "\n%d\n", FNR
printf "\nMacro Call from file: %s\n", $0
}
' $(find "$sitepath/templates/macros" -type f -print)
# restore original IFS and globbing values
IFS="$oIFS"; set +f -"$oSET"
This solves the problem of having spaces in your file names as originally requested, but also handles globbing characters in your file names, and the various typical echo issues.
You can set the internal field separator $IFS (which is normally set to space, tab and newline) to just newline to get around this problem:
IFS="\n"

Check if a particular string is in a file bash

I want to write a script to check for duplicates
For example: I have a text file with information in the format of /etc/passwd
alice:x:1008:555:William Williams:/home/bill:/bin/bash
bob:x:1018:588:Bobs Boos:/home/bob:/bin/bash
bob:x:1019:528:Robt Ross:/home/bob:/bin/bash
james:x:1012:518:Tilly James:/home/bob:/bin/bash
I want to simply check if there are duplicate users and if there are, output the line to standard error. So in the example above since bob appears twice my output would simply generate something like:
Error duplicate user
bob:x:1018:588:Bobs Boos:/home/bob:/bin/bash
bob:x:1019:528:Robt Ross:/home/bob:/bin/bash
Right now I have a while loop that reads each line and stores each piece of information in a variable using awk -F that is delimited with ":". After storing my username I am not too sure on the best approach to check to see if it already exists.
Some parts of my code:
while read line; do
echo $line
user=`echo $line | awk -F : '{print $1}'`
match=`grep $user $1`($1 is the txtfile)
if [ $? -ne 0 ]; then
echo "Unique user"
else
echo "Not unique user"
then somehow grep those lines and output it
fi
done
The matching does not produce the right results
Suggestions?
instead of re-inventing the wheel, use the following tools:
cut to extract first field
sort and uniq to keep duplicated lines only.
cut -d : -f 1 | sort | uniq -d | while read i ; do
echo "error: duplicate user $i"
done
Sounds like a job for awk to me:
% awk -F':' '
/:/ {
count[$1] += 1
}
END {
for (user in count) {
if (count[user] > 1) {
print user " appears in the file " count[user] " times."
}
}
}
' /etc/passwd
A perl-proposal:
perl -F: -lanE 'push #{$h{$F[0]}},$_; END{for $k (keys %h){if(#{$h{$k}}>1){say "Error";say for #{$h{$k}}}}}' file

Resources