Improving knowledge in Bash - bash

This is more directed to learning about BASH rather than creating a specific code.
---Problem: Write a Bash script that takes a list of login names on your computer system as command line arguments, and displays these login names, full names and user-ids of the users who own these logins (as contained in the /etc/passwd file), one per line. If a login name is invalid (not found in the /etc/passwd file), display the login name and an appropriate error message. ---
If I needed to create a code to fulfill this problem could I do it using a "choice" list like this:
read -p "Enter choice: " ch
if [ "$ch" = "1" ]; then
function_1
else
if [ "$ch" = "2" ]; then
function_2
else
if [ "$ch" = "3" ]; then
function_3
else
if [ "$ch" = "4" ]; then
function_4
else
if [ "$ch" = "5" ]; then
function_5
fi x5
or would it have to be completed using a grep and test method where by the user read input must be taken into variables Name1 Name2.... NameN and are tested to the ect/passwd file via grep command.
#!/bin/bash
# pgroup -- first version
# Fetch the GID from /etc/group
gid=$(grep "$̂1:" /etc/group | cut -d: -f3)
# Search users with that GID in /etc/passwd
grep "^[^:]*:[^:]*:[^:]*:$gid:" /etc/passwd | cut -d: -f1`enter code here`
Please explain the best you can with some generic code. I am still learning the basics and testing different ideas. I apologize if these are very vague concepts, I am still getting the hang of BASH.

You would:
Accept (read) from the user the username,
Check if the username exists by retrieving the record using grep,
If positive result (i.e. you got data), display it as needed,
Otherwise, display an error message (or whatever you need).
You are almost there (got how to get input, how to search using grep). What you need is to get the result of the grep into a variable that you can check. You may try something like:
Result=$(grep....)
and then check the contents of Result.

To me it looks like you're missing an important part of the problem: the names are passed as arguments to the script:
./script.sh name1 name2 name3
If this is the case, then a loop would be the most appropriate thing to use:
for login; do
printf '%s: %s\n' "$login" "$(grep "^$login" /etc/passwd)"
done
Note that a for loop iterates over the list of arguments passed to the script $# by default.

Related

how to check whether the input is found or not found in the directory using pinky (there are inputs, but the user is not there)

what I want is to input a username and if the user cannot be found, the FNAME should be not found, but when I did the tester, the NOTFOUND never shows up, the tester was made by someone else and should have no problem.
function fname()
{
#if argument passed canot be found among sessions logged in
result=$(pinky -f "$1")
result_length="${#result}"
#if the result length is not equal to zero means there are some values
elif [ "$result_length" != 0 ]
then
FNAME="$(pinky -f $1 | awk '{print $2}')"
return 0
elif [ ! -f "$result" ]
then
FNAME="NOTFOUND"
return 0
fi
}
I tried to check the result but only ERROR and the user's first name can be displayed, the NOTFOUND never shows up even when I put a wrong input. is it because the ${# result} never display 0?
fname
Function status code is ==1== FNAME value is ==ERROR==
[[[[ WORKS - but user id is not found ]]]]
fname nouser
Function status code is ==0== FNAME value is ====
[[[[ WORKS - user id is found ]]]]
fname zo9
Function status code is ==0== FNAME value is ==Zo==
Your issue is on this line:
elif [ ! -f "$result" ]
This literally means "else if $result is not a file." Since you are setting result to the output of pinky, this is obviously not doing what you think it does. Since you are checking whether the variable result is blank or not in the original if statement, all you need here is else.
Also, there is a built in way to check for a blank variable using test ([). You could test with the -n option. It would be cleaner to use that in your if block like this:
if [ -n "$result" ]; then
# found
else
# not found
fi
Then, there is no need for the result_length variable.
Also, there is no need to re-run pinky for awk. You have the result of pinky stored in your result variable. All you need to do is send the variable into awk with a here-string. Something like this:
FNAME="$(awk '{print $2}' <<< "$result")"

simple password cracker in scripting doesn't end when it find the correct password

#!/bin/bash
commonguess(){
for guess in $(cat passwordlist)
do
try=$(echo "$guess" | sha256sum | awk '{print $1}' )
if [ "$try" == "$xxx" ]
then
echo "$name:$try"
break
fi
done
}
dict(){...}
brute(){...}
while IFS=':' read -r name hashing;do
commonguess
dict
brute
done
I have tested all 3 functions, they work fine. The only problem is in the bottom.
a:123
b:234
c:111
For example, i have this file named "user". If i run my code with this file, my code will test value of a(123) through 3 functions even it found answer in the first one. I don't want to do that, i want to do something like testing the value, if not found in function1 then move to next function, if it find answer then just move to next value.
Replace break in commonguess for "return 0", add "return 1" to the end of the function, do the same for the other functions and change the while loop to:
while IFS=':' read -r name hashing;do
commonguess || dict || brute
done
More information about the bash conditional short circuit evaluation here:
http://wresch.github.io/2014/04/24/bash-short-circuit-evaluation.html

Making code generic

I have one script /location/centers_password.sh in which I have below lines of code :
if [ "$DC" = "sl" ]
then
arg1=`echo -n $Center|wc -m`
if [ $arg1 -gt 3 ]
then
grep "^$Center" /location/.CenterPassword.file.sdi.sl
grep "^$Center" /location/.CenterPassword_xel.file.sdi.sl
else
grep "^$Center" /location/.CenterPassword.file.sl
grep "^$Center" /location/.CenterPassword_xel.file.sl
grep "^$Center" /location/.sftpPasswod.file.sl
fi
elif [ "$DC" = "ch" ]
then
arg1=`echo -n $Center|wc -m`
if [ $arg1 -gt 3 ]
then
grep "^$Center" /location/.CenterPassword.file.sdi.ch
grep "^$Center" /location/.CenterPassword_xel.file.sdi.ch
else
grep "^$Center" /location/.CenterPassword.file.ch
grep "^$Center" /location/.CenterPassword_xel.file.ch
grep "^$Center" /location/.sftpPasswod.file.ch
fi
What I am doing here is, when I execute this code it reads the datacenter name .CenterPassword.file.sdi.ch file. If I explain more, suppose the datacenter starts from 'sl' it reads .CenterPassword.file.sdi.sl file and fetch the password and suppose datacenter starts from 'ch' it reads .CenterPassword.file.sdi.ch and fetch the password.
My requirement is to make this code generic. This file should read the hostname and if the hostname's initial two letter match with .CenterPassword.file.sdi.ch file last two letters (in this example, should match with 'ch') it should fetch the password. Please help to do this.
Problem 1: get the first two letters of the hostname:
DC=`hostname | cut -b1,2`
Problem 2: Use that to read the right filenames:
You could just use your long list of if/else statements (how many datacenters can there be?), but as shell lets you build a filename on the fly, there's no need.
grep "^Center" /location/.CenterPassword.file.sdi.$DC
The $DC will add the proper two-letter suffix to the filename.

Find FileVault2 user in fdesetup output with Awk

I have 2 user profiles (fred, fredmoo) with filevault 2 enabled.
I have the following bash:
## Get the logged in user's name
userName=$(/usr/bin/stat -f%Su /dev/console)
This first user check sees if the logged in account is already authorized with FileVault 2
userCheck=`fdesetup list | awk -v usrN="$userName" -F, 'index($0, usrN) {print $1}'`
if [ "${userCheck}" != "${userName}" ]; then
echo "This user is not a FileVault 2 enabled user."
exit 3
fi
If I do echo $userName, I get
fred
If I do echo $userCheck, I get
fred fredmoo
The conditional statement above works well if there's only 1 profile. Unix or Linux Image, however since my mac has more than 1 user profile, the statement will echo "This user is not a FileVault 2 enabled user." and exit.
userCheck has both profiles.
How do I modify the if statement to say if userName does NOT equal to the 1st userCheck or 2nd userCheck, then echo "This user is not a FileVault 2 enabled user." and exit?
The output from fdesetup list looks like this:
fredmoo,485A09Cf-B0D5-469A-8224-2DD1877E780B
administrator,DDB87E8D-8150-4D06-A59D-774AD28D119C
gollum,8AE6C365-E38F-49E2-998C-F4742CC9980C
Your Awk script looks for fred anywhere in the fdesetup output, which of course it also finds when the output contains fredmoo.
You seem to have a comma-separated output that you are comparing against, so your Awk script should probably be something like awk -v user="$(whoami)" -F , '$1 == user { print $1 }' (where I assume output looks like in the sample here: https://derflounder.wordpress.com/2013/10/22/managing-mavericks-filevault-2-with-fdesetup/).
Also, a common antipattern is storing stuff you only need once in a variable, which just complicates things and pollutes your namespace. Try to change your Awk script so that it sets the correct exit code; then you can use that directly in a conditional. Maybe even refactor that into a reusable function.
warn () {
echo "$0: $#" >&2
}
die () {
rc=$1
shift
warn "$#"
exit $rc
}
canihazfv2 () {
fdesetup list |
awk -v user="$1" -F , '$1 == user { print $1; exit 0 } END { exit 1 }'
}
me=$(whoami)
canihazfv2 "$me" || die 3 "$me is not enabled to use FileVault 2"
Notice also how the script identifies itself in the error message (so that when you build new scripts which call other scripts which call this script, you can see which one is actually failing three years from now) as well as the actual input which triggered the error, and prints the error message to standard error through redirection.
As always, this || that is shorthand for if ! this; then that; fi where of course the longhand might be preferable if that is something moderately complex (which I avoid here by also encapsulating die in a function).

read stdin in function in bash script

I have some set of bash functions which output some information:
find-modelname-in-epson-ppds
find-modelname-in-samsung-ppds
find-modelname-in-hp-ppds
etc ...
I've been writing functions which read output and filter it:
function filter-epson {
find-modelname-in-epson-ppds | sed <bla-blah-blah>
}
function filter-hp {
find-modelname-in-hp-ppds | sed <the same bla-blah-blah>
}
etc ...
But the I thought that it would be better do something like this:
function filter-general {
(somehow get input) | sed <bla-blah-blah>
}
and then call in another high-level functions:
function high-level-func {
# outputs filtered information
find-modelname-in-hp/epson/...-ppds | filter-general
}
How can I achieve that with the best bash practices?
If the question is How do I pass stdin to a bash function?, then the answer is:
Shellscript functions take stdin the ordinary way, as if they were commands or programs. :)
input.txt:
HELLO WORLD
HELLO BOB
NO MATCH
test.sh:
#!/bin/sh
myfunction() {
grep HELLO
}
cat input.txt | myfunction
Output:
hobbes#metalbaby:~/scratch$ ./test.sh
HELLO WORLD
HELLO BOB
Note that command line arguments are ALSO handled in the ordinary way, like this:
test2.sh:
#!/bin/sh
myfunction() {
grep "$1"
}
cat input.txt | myfunction BOB
Output:
hobbes#metalbaby:~/scratch/$ ./test2.sh
HELLO BOB
To be painfully explicit that I'm piping from stdin, I sometimes write
cat - | ...
A very simple means to get stdin into a variable is to use read. By default, it reads file descriptor "0", i.e. stdin i.e., /dev/stdin.
Example Function:
input(){ local in; read in; echo you said $in; }
Example implementation:
echo "Hello World" | input
Result:
you said Hello World
Additional info
You don't need to declare a variable as being local, of course. I just included that for the sake of good form. Plain old read in does what you need.
So you understand how read works, by default it reads data off the given file descriptor (or implicit stdin) and blocks until it encounters a newline. Much of the time, you'll find that will implicitly be attached to your input, even if you weren't aware of it. If you have a function that seems to hang with this mechanism just keep this detail in mind (there are other ways of using read to deal with that...).
More robust solutions
Adding on to the basic example, here's a variation that lets you pass the input via a stdin OR an argument:
input()
{
local in=$1; if [ -z "$in" ]; then read in; fi
echo you said $in
}
With that tweak, you could ALSO call the function like:
input "Hello World"
How about handling an stdin option plus other arguments? Many standard nix utilities, especially those which typically work with stdin/stdout adhere to the common practice of treating a dash - to mean "default", which contextually means either stdin or stdout, so you can follow the convention, and treat an argument specified as - to mean "stdin":
input()
{
local a=$1; if [ "$a" == "-" ]; then read a; fi
local b=$2
echo you said $a $b
}
Call this like:
input "Hello" "World"
or
echo "Hello" | input - "World"
Going even further, there is actually no reason to only limit stdin to being an option for only the first argument! You might create a super flexible function that could use it for any of them...
input()
{
local a=$1; if [ "$a" == "-" ]; then read a; fi
local b=$2; if [ "$b" == "-" ]; then read b; fi
echo you said $a $b
}
Why would you want that? Because you could formulate, and pipe in, whatever argument you might need...
myFunc | input "Hello" -
In this case, I pipe in the 2nd argument using the results of myFunc rather than the only having the option for the first.
Call sed directly. That's it.
function filter-general {
sed <bla-blah-blah>
}

Resources