check if a username appears in the output of who - linux

The task requires that a bash script be written that will search the "who" command for a given user ID which will be provided via command line argument
This script will display whether or not this user ID is logged in
So far I know that to get the user ID, one can do:
who | cut -d' ' -f1 | grep "userIdToSearchFor"
This grep will display the user ID if it exists, or nothing if it doesn't, so it seems like a good method
I believe the $1 variable will hold the first command line argument
How can I implement this in a bash script file please?
EDIT:
Current working script looks like this
#!/bin/bash
userid=$(who | cut -d' ' -f1 | grep "$1")
if [ "$1" == "$userid" ]
then
echo "online"
else
echo "offline"
fi

This should work for you :
STRING=$(who | cut -d' ' -f1 | grep "$1")
if [ "$1" = "$STRING" ]
then
echo "online"
else
echo "offline"
fi
Some comments and suggestions :
No spaces on both sides of the = when you assign variables (that's where your error message come from).
To assign commands result to a variable, you must use the $( ) syntax. See command substitution for more.
Quote you vars in your test to prevent word splitting.
You should loop on the test, there could be multiple identical usernames.
Avoid caps in you variable names not to confuse with environment variables which are capitalized by convention.
Avoid to use the type of the var for its name, in your case username would be a better choice.

You're doing it the hard way.
$ cat user.sh
#!/bin/bash
# user.sh username - shows whether username is logged on or not
if who | grep --silent "^$1 " ; then
echo online
else
echo offline
fi
$ ./user.sh msw
online

Related

How to avoid magic-numbers in shell?

I always write some magic numbers in my interactive shells and shell scripts.
For instance, If I want to list my users's names and shells, I'll write
cut --delimiter=: --fields=1,7 /etc/passwd
There exist two magic-numbers 1,7. And there are more and more magic-numbers in other circumstances.
Question
How to avoid magic-numbers in interactive shells and shell scripts?
Supplementary background
Our teacher told us using cut -d: -f1,7 /etc/passwd. But for new linux-users, they don't konw what's meaning of d,f,1,7.(not just for new linux-users,the whole system has so many configuration files that it is not easy for a person to remember every magic-numbers)
So, in interactive shells, we can use --delimiter, --fields,and the bash repl(or zsh,fish) has good tab completion to it.
How about the 1 and 7? In shell scripts, It's a good method to declare some const variables like LoginField=1 and ShellField=7 after reading the man 5 passwd. But when some one is writing in the interactive shells, it's not a good idea to open a new window and search the constants of LoginField=1,ShellField=7 and define it. how to using some thing like tab completion to simplify operations?
Use variables:
LoginField=1 ShellField=7
cut --delimiter=: --fields="$LoginField,$ShellField" /etc/passwd
Just like in other languages - by using variables. Example:
$ username_column=1
$ shell_column=7
$ cut --delimiter=: --fields="$username_column","$shell_column" /etc/passwd
The variables may be defined at the top of the script so that can be
easily modified or they can be set in an external config-like file
shared by multiple scripts.
The classic way to parse /etc/passwd is to read each column into an appropriately named variable:
while IFS=: read name passwd uid gid gecos home shell _; do
...
done < /etc/passwd
Use export:
export field_param="1,7"
(you can put it .bashrc file to have configured each time shell session is started). This export can be part of .sh script. It's a good practice to put them in the head/top of the file.
Then:
cut --delimiter=: --fields=$field_param /etc/passwd
This way you will need to edit the magic number in the only location.
Continuing from my comment, it's hard to tell exactly what you are asking. If you just want to give meaningful variable names, then do as shown in the other answers.
If however you want to be able to specify which fields are passed to cut from the command line, then you can use the positional parameters $1 and $2 to pass those values into your script.
You need to validate that two inputs are given and that both are integers. You can do that with a few simple tests, e.g.
#!/bin/bash
[ -n "$1" ] && [ -n "$2" ] || { ## validate 2 parameters given
printf "error: insufficient input\nusage: %s field1 field2\n" "${0##*/}"
exit 1
}
## validate both inputs are integer values
[ "$1" -eq "$1" >/dev/null 2>&1 ] || {
printf "error: field1 not integer value '%s'.\n" "$1"
exit 1
}
[ "$2" -eq "$2" >/dev/null 2>&1 ] || {
printf "error: field2 not integer value '%s'.\n" "$2"
exit 1
}
cut --delimiter=: --fields=$1,$2 /etc/passwd
Example Use/Output
$ bash fields.sh
error: insufficient input
usage: fields.sh field1 field2
$ bash fields.sh 1 d
error: field2 not integer value 'd'.
$ bash fields.sh 1 7
root:/bin/bash
bin:/usr/bin/nologin
daemon:/usr/bin/nologin
mail:/usr/bin/nologin
ftp:/usr/bin/nologin
http:/usr/bin/nologin
uuidd:/usr/bin/nologin
dbus:/usr/bin/nologin
nobody:/usr/bin/nologin
systemd-journal-gateway:/usr/bin/nologin
systemd-timesync:/usr/bin/nologin
systemd-network:/usr/bin/nologin
systemd-bus-proxy:/usr/bin/nologin
<snip>
Or if you choose to look at fields 1 and 3, then all you need do is pass those as the parameters, e.g.
$ bash fields.sh 1 3
root:0
bin:1
daemon:2
mail:8
ftp:14
http:33
uuidd:68
dbus:81
nobody:99
systemd-journal-gateway:191
systemd-timesync:192
systemd-network:193
systemd-bus-proxy:194
<snip>
Look things over and let me know if you have further questions.
Scraping the output of man 5 passwd for human-readable header names:
declare $(man 5 passwd |
sed -n '/^\s*·\s*/{s/^\s*·\s*//;y/ /_/;p}' |
sed -n 'p;=' | paste -d= - - )
See "how it works" below for what that does, then run:
cut --delimiter=: \
--fields=${login_name},${optional_user_command_interpreter} /etc/passwd
Which outputs the specified /etc/passwd fields.
How it works.
The man page describing /etc/passwd contains a bullet list of header names. Use GNU sed to find the bullets (·) and leading whitespace, then remove the bullets and whitespace, replace the remaining spaces with underlines; a 2nd instance of sed provides fresh line numbers, then paste the header names to the line numbers, with a = between:
man 5 passwd |
sed -n '/^\s*·\s*/{s/^\s*·\s*//;y/ /_/;p}' |
sed -n 'p;=' | paste -d= - -
Outputs:
login_name=1
optional_encrypted_password=2
numerical_user_ID=3
numerical_group_ID=4
user_name_or_comment_field=5
user_home_directory=6
optional_user_command_interpreter=7
And declare makes those active in the current shell.

Mail output with Bash Script

SSH from Host A to a few hosts (only one listed below right now) using the SSH Key I generated and then go to a specific file, grep for a specific word with a date of yesterday .. then I want to email this output to myself.
It is sending an email but it is giving me the command as opposed to the output from the command.
#!/bin/bash
HOST="XXXXXXXXXXXXXXXXXX, XXXXXXXXXXXXX"
DATE=$(date -d "yesterday")
INVALID=' cat /xxx/xxx/xxxxx | grep 'WORD' | sed 's/$/.\n/g' | grep "$DATE"'
COUNT=$(echo "$INVALID" | wc -c)
for x in $HOSTS
do
ssh BLA#"$x" $COUNT
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT=""
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT="$INVALID"
fi
fi
done | echo -e "$EMAILTEXT" | mail XXXXXXXXXXX.com
This isn't properly an attempt to answer your question, but I think you should be aware of some fundamental problems with your code.
INVALID=' cat /xxx/xxx/xxxxx | grep 'WORD' | sed 's/$/.\n/g' | grep "$DATE"'
This assigns a simple string to the variable INVALID. Because of quoting issues, s/$/.\n/g is not quoted at all, and will probably be mangled by the shell. (You cannot nest single quotes -- the first single-quoted string extends from the first quote to the next one, and then WORD is outside of any quotes, followed by the next single-quoted string, etc.)
If your intent is to execute this as a command at this point, you are looking for a command substitution; with the multiple layers of uselessness peeled off, perhaps something like
INVALID=$(sed -n -e '/WORD/!d' -e "/$DATE/s/$/./p" /xxx/xxx/xxxx)
which looks for a line matching WORD and $DATE and prints the match with a dot appended at the end -- I believe that's what your code boils down to, but without further insights into what this code is supposed to do, it's impossible to tell if this is what you actually need.
COUNT=$(echo "$INVALID" | wc -c)
This assigns a number to $COUNT. With your static definition of INVALID, the number will always be 62; but I guess that's not actually what you want here.
for x in $HOSTS
do
ssh BLA#"$x" $COUNT
This attempts to execute that number as a command on a number of remote hosts (except the loop is over HOSTS and the variable containing the hosts is named just HOST). This cannot possibly be useful, unless you have a battery of commands named as natural numbers which do something useful on these remote hosts; but I think it's safe to assume that that is not what is supposed to be going on here (and if it was, it would absolutely be necessary to explain this in your question).
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT=""
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT="$INVALID"
fi
fi
So EMAILTEXT is either an empty string or the value of INVALID. You assigned it to be a static string above, which is probably the source of your immediate question. But even if it was somehow assigned to a command on the local host, why do you need to visit remote hosts and execute something there? Or is your intent actually to execute the command on each remote host and obtain the output?
done | echo -e "$EMAILTEXT" | mail XXXXXXXXXXX.com
Piping into echo makes no sense at all, because it does not read its standard input. You should probably just have a newline after done; though a possibly more useful arrangement would be to have your loop produce output which we then pipe to mail.
Purely speculatively, perhaps something like the following is what you actually want.
for host in $HOSTS; do
ssh BLA#"$host" sed -n -e '/WORD/!d' -e "/$DATE/s/$/./p" /xxx/xxx/xxxx |
grep . || echo INVALID
done | mail XXXXXXXXXXX.com
If you want to check that there is strictly more than one line of output (which is what the -gt 1 suggests) then this may need to be a little bit more complicated.
Your command substitution is not working. You should read up on how it works but here are the problem lines:
COUNT=$(echo "$INVALID" | wc -c)
[...]
ssh BLA#"$x" $COUNT
should be:
COUNT_CMD="'${INVALID} | wc -c'"
[...]
COUNT=$(ssh BLA#"$x" $COUNT_CMD)
This inserts the value of $INVALID into the string, and puts the whole thing in single quotes. The single quotes are necessary for the ssh call so the pipes aren't evaluated in the script but on the remote host. (COUNT is changed to COUNT_CMD for readability/clarity.)
EDIT:
I misread the question and have corrected my answer.

Bash loop is not working — cannot find command "[0%"

I just wrote a ping sweep script in Bash this morning, and guess what: it's not working. Can you please check what it is that I'm missing.
Here's the script:
for i in `seq 1 255`
do
if ["$(ping -c1 -W1 -n 192.168.1.$i | grep '%' | cut -d',' -f3 | cut -d' ' -f2)" -eq "0%"]
then echo "Host live"
else echo "Host down"
fi
done
And here's the error:
bash: [0%: command not found
Host down
bash: [100%: command not found
Host down
My purpose is to make a ping sweep program which scans the range 192.168.1.1-255 and it notifies the host's status. I know about nmap but just wanted to learn skills in Bash so I made this one. Please try to tell what the error meant. I mean to what command it's referring "command not found"?
The ping command returns error code if there was any problem, so you do not need to parse the output:
for i in {1..255}
do
if ping -c1 -W1 -n "192.168.1.$i"
then
echo 'Host live'
else
echo 'Host down'
fi
done
Primary diagnosis
The [ command needs a space after its name, just like the rm command needs a space after its name and the ls command does, and … The [ command also requires its last argument to be ], spelled thus, so there needs to be a space before that, too.
You have:
if ["$(ping -c1 -W1 -n 192.168.1.$i | grep '%' | cut -d',' -f3 | cut -d' ' -f2)" -eq "0%"]
At minimum, you need:
if [ "$(ping -c1 -W1 -n 192.168.1.$i | grep '%' | cut -d',' -f3 | cut -d' ' -f2)" -eq "0%" ]
Secondary issues
Note that 'at minimum' means, amongst other things, that I've not spent time analyzing why you are executing the complex sequence of 4 commands in the test condition, or looked for ways to cut that down to two (using grep and cut twice suggests that sed or a more powerful tool would be better). I griped about the formatting in the original version of the question, where the loop (it isn't a nested loop, incidentally — or it isn't in the code shown) was all on one line thanks to Bash flattening it in history. My version of the code would have far fewer semicolons in it, for example. The -eq operator in [ is for testing the equality of numbers (the converse convention applies in Perl, where eq is for testing strings and == tests numbers). Note that POSIX standard [ (aka test) does not support == as a synonym for =, though Bash does. It isn't entirely clear that "0%" is OK as an argument for numeric comparison. Many programs would not object — the zero can be converted and the residue doesn't matter; others might decide legitimately to complain that the whole string could not be converted, so it is erroneous. Careful code wouldn't risk the disconnect.
See Steven Penny's answer for a more thorough rewrite of the code. My answer remains a valid diagnosis of the immediate problem of not being able to find commands named [0% and [100%.

Variable within a variable in a CGI script

There is such a script:
NAME = `echo "$QUERY_STRING" | sed -n 's/^.*post=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
RES = `psql -U user -d db -t -c "SELECT tabl FROM tablica WHERE name = '$NAME'"`
echo $RES
Everything works fine (it means GET requests are fine). But the data from the database do not go.
The problem is that the value of the parameter in the query WHERE NAME is not being set, and I get a syntax error.
I have read many articles on the Internet, but found nothing about a variable inside backticks.
How can I fix this?
You're not allowed to put spaces around the equal sign in variable assignments. And you should generally not use backticks, but prefer the $() form, it is easier to deal with quoting with it.
NAME=$(echo "$QUERY_STRING" | sed -n 's/^.*post=\([^&]*\).*$/\1/p' | sed "s/%20/ /g")
RES=$(psql -U user -d db -t -c "SELECT tabl FROM tablica WHERE name = '$NAME'")
echo "$RES"
Note that what you're doing is pretty insecure, you need stronger validation for your inputs.

Assigning a variable after the contents are 'cut' in bash

I am iterating through a folder of files using bash, but I need to cut the preceding path. For instance if I have this '/temp/test/filename' I want to cut off the '/temp/test/' and store the file name to a variable so I can write a log with the filename in it.
Can anyone help me out? The problem is that the variable temp is always empty.
Here is my bash code:
#!/bin/bash
for file in /temp/test/*
do
if [[ ! -f "$file" ]]
then
continue
fi
temp="$file"|cut -d'/' -f3
$file > /var/log/$temp$(date +%Y%m%d%H%M%S).log
done
exit
Try that :
$ x=/temp/test/filename
$ echo ${x##*/}
filename
Another solution is to use basename :
$ basename /temp/test/filename
filename
The first solution is a parameter expansion and it's a bash builtin, so we increase performance.
Your line temp="$file"|cut -d'/' -f3 is broken.
when you want to store the output of a command in a variable, you should do var=$(command)
you need to pass the value to the STDIN of the command with a here-string (<<<) or with echo value | command
finally, if you'd want to use cut :
$ temp=$(cut -d/ -f4 <<< /temp/test/filename)
$ echo $temp
filename

Resources