A shell script that echo’s the commands output to the terminal - linux

A shell script that echo’s the output of the command to the terminal, so far, it’s only outputting empty space. How can I get the desired result? I've tried echoing the variable itself without any luck so I'm guessing the issue is with the command itself.
Actual result:
~$
Preferable result:
Name N. Name
Name N. Name
Name N. Name
Name N. Name
#! /bin/bash
function ag_students() {
ag_names=$(grep -i ^[A-G] /etc/passwd | cut -d: -f5 /etc/passwd > tmp | echo "$tmp" ;)
echo "$tmp"
}
$ag_students
echo "Here are the names: $tmp"

Source code, with some comments:
#! /bin/bash
function ag_students() {
# grep -i ^[A-G] -> grep -i [a-g] # it is absolutely same but you do not need to hit your [shift] key repeatedly.
# <command> | cut -d: -f5 /etc/passwd
# It is stange for me. From the one point of view you want to handle the output
# of the <command1> using <command2> ("cut" utility).
# From the other side you specified the input file for it (/etc/passwd)
# Look at the "cut --help"
# Thus it should be:
# grep -i ^[a-g] /etc/passwd | cut -d: -f5
# without any additions
# Next: "<command> > <name>" will store its output to the file <name>, not to the variable <name>
# "cut -d: -f5 /etc/passwd > tmp" will store its output to the file "tmp", not to the variable with same name
# finally, because, according above, variable "$tmp" still not initialized,
# "echo "$tmp"" will return the empty string whith is assigned to "ag_names" variable
# ag_names=$(grep -i ^[A-G] /etc/passwd | cut -d: -f5 /etc/passwd > tmp | echo "$tmp" ;)
# The right solution:
ag_names=$(grep -i [a-g] /etc/passwd | cut -d: -f5)
# or simpy
grep -i [a-g] /etc/passwd | cut -d: -f5
# It is unnecessary because previous bunch of command alredy output what you want
# echo "$tmp"
}
# Using dollar sign you are trying to execute command contains in the "ag_students" variable (which is not initialized/empty)
# and executing something empty you will get empty result also
$ag_students
# to execute a function - execute it as any other command:
ag_students
# And if you want to have its result in some variable:
tmp=$(ag_students) # as usual
echo "Here are the names: $tmp"
As conclusion, unrelated to the logic, your script should be:
#!/bin/bash
function ag_students() {
grep -i ^[a-g] /etc/passwd | cut -d: -f5
}
tmp=$(ag_students)
echo "Here are the names: $tmp"

Related

linux bash saved variable is empty

I'm new to shell. I try to get a string in file and use it to make a shortcut link.
tmp/tempfile.tmp
/mnt/sda5
in my test.sh file:
#!/bin/sh
#working just fine!
echo $(cat /tmp/tempfile.tmp | cut -d' ' -f2)
#empty result
USBdev = $(cat /tmp/tempfile.tmp | cut -d' ' -f2)
echo ${USBdev}/
# --making a link
ln -s ${USBdev} $(pwd)/C:
exit 0
why it is empty??
echo ${USBdev} --result is empty!
I'm using this on my openwrt router.
I tried as follow, still empty
echo "$USBdev"
echo "${USBdev%.*}"
SOLUTION:
REMOVE "SPACES" AROUND THE "=", thanks to CDroescher
change
USBdev = $(...)
to (remove spaces)
USBdev=$(...)
You have to add a shebang at the first line https://en.wikipedia.org/wiki/Shebang_(Unix) and make sure that there are no spaces after and before "="
#!/usr/bin/env bash
#working just fine!
echo $(cat /tmp/tempfile.tmp | cut -d' ' -f2)
#empty result
USBdev=$(cat /tmp/tempfile.tmp | cut -d' ' -f2)
echo ${USBdev}/
# --making a link
ln -s ${USBdev} $(pwd)/C:

Back slash (\) should not be printed with echo command

I have a parameter file (param.env) having the following content.
MY_PARAM=com:27}WMV\)pviZN
also, a bash file where I am fetching the value of MY_PARAM and writing into a random file.
#!/bin/bash
value=$(grep "^MY_PARAM=" param.env | cut -d '=' -f2-)
value1=$(cat param.env | grep "^MY_PARAM" | sed 's/=/ /' | awk '{print $2}')
echo $value
echo $value1
printf '%s\n' "$value"
Output:
com:27}WMV\)pviZN
com:27}WMV\)pviZN
com:27}WMV\)pviZN
However, I am expecting \ to be escaped and should not be part of the output.
I am also not allowed to edit the param.env.
Expected output:
com:27}WMV)pviZN
You could source the file, then the string will behave as if you'd assigned it like that in an interactive shell:
$ (. param.env; echo "$MY_PARAM")
com:27}WMV)pviZN
I've put the commands in a subshell so they don't pollute the environment.

Linux usernames /etc/passwd listing

I want to print the longest and shortest username found in /etc/passwd. If I run the code below it works fine for the shortest (head -1), but doesn't run for (sort -n |tail -1 | awk '{print $2}). Can anyone help me figure out what's wrong?
#!/bin/bash
grep -Eo '^([^:]+)' /etc/passwd |
while read NAME
do
echo ${#NAME} ${NAME}
done |
sort -n |head -1 | awk '{print $2}'
sort -n |tail -1 | awk '{print $2}'
Here the issue is:
Piping finishes with the first sort -n |head -1 | awk '{print $2}' command. So, input to first command is provided through piping and output is obtained.
For the second command, no input is given. So, it waits for the input from STDIN which is the keyboard and you can feed the input through keyboard and press ctrl+D to obtain output.
Please run the code like below to get desired output:
#!/bin/bash
grep -Eo '^([^:]+)' /etc/passwd |
while read NAME
do
echo ${#NAME} ${NAME}
done |
sort -n |head -1 | awk '{print $2}'
grep -Eo '^([^:]+)' /etc/passwd |
while read NAME
do
echo ${#NAME} ${NAME}
done |
sort -n |tail -1 | awk '{print $2}
'
All you need is:
$ awk -F: '
NR==1 { min=max=$1 }
length($1) > length(max) { max=$1 }
length($1) < length(min) { min=$1 }
END { print min ORS max }
' /etc/passwd
No explicit loops or pipelines or multiple commands required.
The problem is that you only have two pipelines, when you really need one. So you have grep | while read do ... done | sort | head | awk and sort | tail | awk: the first sort has an input (i.e., the while loop) - the second sort doesn't. So the script is hanging because your second sort doesn't have an input: or rather it does, but it's STDIN.
There's various ways to resolve:
save the output of the while loop to a temporary file and use that as an input to both sort commands
repeat your while loop
use awk to do both the head and tail
The first two involve iterating over the password file twice, which may be okay - depends what you're ultimately trying to do. But using a small awk script, this can give you both the first and last line by way of the BEGIN and END blocks.
While you already have good answers, you can also use POSIX shell to accomplish your goal without any pipe at all using the parameter expansion and string length provided by the shell itself (see: POSIX shell specifiction). For example you could do the following:
#!/bin/sh
sl=32;ll=0;sn=;ln=; ## short len, long len, short name, long name
while read -r line; do ## read each line
u=${line%%:*} ## get user
len=${#u} ## get length
[ "$len" -lt "$sl" ] && { sl="$len"; sn="$u"; } ## if shorter, save len, name
[ "$len" -gt "$ll" ] && { ll="$len"; ln="$u"; } ## if longer, save len, name
done </etc/passwd
printf "shortest (%2d): %s\nlongest (%2d): %s\n" $sl "$sn" $ll "$ln"
Example Use/Output
$ sh cketcpw.sh
shortest ( 2): at
longest (17): systemd-bus-proxy
Using either pipe/head/tail/awk or the shell itself is fine. It's good to have alternatives.
(note: if you have multiple users of the same length, this just picks the first, you can use a temp file if you want to save all names and use -le and -ge for the comparison.)
If you want both the head and the tail from the same input, you may want something like sed -e 1b -e '$!d' after you sort the data to get the top and bottom lines using sed.
So your script would be:
#!/bin/bash
grep -Eo '^([^:]+)' /etc/passwd |
while read NAME
do
echo ${#NAME} ${NAME}
done |
sort -n | sed -e 1b -e '$!d'
Alternatively, a shorter way:
cut -d":" -f1 /etc/passwd | awk '{ print length, $0 }' | sort -n | cut -d" " -f2- | sed -e 1b -e '$!d'

Append all files to one single file in unix and rename the output file with part of first and last filenames

For example, I have below log files from the 16th-20th of Feb 2015. Now I want to create a single file named, mainentrywatcherReport_2015-02-16_2015-02-20.log. So in other words, I want to extract the date format from the first and last file of week (Mon-Fri) and create one output file every Saturday. I will be using cron to trigger the script every Saturday.
$ ls -l
mainentrywatcher_2015-02-16.log
mainentrywatcher_2015-02-17.log
mainentrywatcher_2015-02-18.log
mainentrywatcher_2015-02-19.log
mainentrywatcher_2015-02-20.log
$ cat *.log >> mainentrywatcherReport_2015-02-16_2015-02-20.log
$ mv *.log archive/
Can anybody help on how to rename the output file to above format?
Perhaps try this:
parta=`ls -l | head -n1 | cut -d'_' -f2 | cut -d'.' -f1`
partb=`ls -l | head -n5 | cut -d'_' -f2 | cut -d'.' -f1`
filename=mainentrywatcherReport_${parta}_${partb}.log
cat *.log >> ${filename}
"ls -l" output is described in the question
"head -nX" takes the Xth line of the output
"cut -d'_' -f2" takes everything (that remains) after the first underscore
"cut -d'.' -f1" times everything (that remains) before the first period
both commands are surrounded by ` marks (above tilde ~) to capture the output of the command to a variable
file name assembles the two dates stripped of the unnecessary with the other formatting desired for the final file name.
the cat command demonstrates one possible way to use the resulting filename
Happy coding! Leave a comment if you have any questions.
You can try this if you want to introduce simple looping...
FROM=ls -lrt mainentrywatcher_* | awk '{print $9}' | head -1 | cut -d"_" -f2 | cut -d"." -f1
TO=ls -lrt mainentrywatcher_* | awk '{print $9}' | tail -1 | cut -d"_" -f2 | cut -d"." -f1
FINAL_LOG=mainentrywatcherReport_${FROM}_${TO}.log
for i in ls -lrt mainentrywatcher_* | awk '{print $9}'
do
cat $i >> $FINAL_LOG
done
echo "All Logs Stored in $FINAL_LOG"
Another approach given your daily files and test contents as follows:
mainentrywatcher_2015-02-16.log -> a
mainentrywatcher_2015-02-17.log -> b
mainentrywatcher_2015-02-18.log -> c
mainentrywatcher_2015-02-19.log -> d
mainentrywatcher_2015-02-20.log -> e
That utilizes bash parameter expansion/substring extraction would be a simple loop:
#!/bin/bash
declare -i cnt=0 # simple counter to determine begin
for i in mainentrywatcher_2015-02-*; do # loop through each matching file
tmp=${i//*_/} # isolate date
tmp=${tmp//.*/}
[ $cnt -eq 0 ] && begin=$tmp || end=$tmp # assign first to begin, last to end
((cnt++)) # increment counter
done
cmbfname="${i//_*/}_${begin}_${end}.log" # form the combined logfile name
cat ${i//_*/}* > $cmbfname # cat all into combined name
## print out begin/end/cmbfname & contents to verify
printf "\nbegin: %s\nend : %s\nfname: %s\n\n" $begin $end $cmbfname
printf "contents: %s\n\n" $cmbfname
cat $cmbfname
exit 0
use/output:
alchemy:~/scr/tmp/stack/tmp> bash weekly.sh
begin: 2015-02-16
end : 2015-02-20
fname: mainentrywatcher_2015-02-16_2015-02-20.log
contents: mainentrywatcher_2015-02-16_2015-02-20.log
a
b
c
d
e
You can, of course, modify the for loop to accept a positional parameter containing the partial filename and pass the partial file name from the command line.
Something like this:
#!/bin/sh
LOGS="`echo mainentrywatcher_2[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].log`"
HEAD=
TAIL=
for logs in $LOGS
do
TAIL=`echo $logs | sed -e 's/^.*mainentrywatcher_//' -e 's/\.log$//'`
test -z "$HEAD" && HEAD=$TAIL
done
cat $LOGS >mainentrywatcherReport_${HEAD}_${TAIL}.log
mv $LOGS archive/
That is:
get a list of the existing logs (which happen to be sorted) in a variable $LOGS
walk through the list, getting just the date according to the example
save the first date as $HEAD
save the last date as $TAIL
after the loop, cat all of those files into the new output file
move the used-up log-files into the archive directory.

Grep only Variable Names in Bash script

I have a bash script A, I am writing another script B which prints the variable names which are exported in script A.
Example:
export A=TRUE
export ABC="GFT"
export XYZ="FFF"
\# export P="SSD"
If I execute the script A, it has to give output as follows:
A
ABC
XYZ
I have tried this oneline:
grep export file.env | grep -v "#" | cut -d' ' -f2 | cut -d'=' -f1
But the problem is, if the export is not start from beginning of the line i.e. if there is some space/Tab in the beginning, the above online doesn't work.
Any alternate Solution for this ?
awk '$1 == "export" { split($2, a, "="); print a[1]; }'
A solution using grep:
grep --perl-regexp --only-matching '\s*export\s+([A-Za-z0-9_]+)='
b.sh:
printenv > TMP
. ./a.sh
printenv | diff TMP - | grep -v printenv | sed -n 's/> *\([^ =]*\)=.*/\1/p'
Or to get the changes:
set > TMP
. ./a.sh
set | grep -vf - TMP | cut -d= -f1
this might give you some extra variables that change such as BASH_LINENO.
sed -n '/^[[:blank:]]*export[[:blank:]]*\([^=]\{1,\}\)=.*/ s//\1/p' ScriptA
[[:blank:]] could be replaced by a simple space in most of the case

Resources