What is the best way to evaluate two variables representing a single pipeline command in bash? - linux

I have a function produce which determines whether a file is present and if not it runs the following command. This works fine when the command output simply writes to stdout. However in the command below I pipe the output to a second command and then to a third command before it outputs to stdout. In this scenario I get the output writing to file correctly but it does not echo the preceding $# from the produce function and the contents of the initial unpopulated outputfile.vcf (contains header columns) which is generated by the pipeline command on execution is also being outputted to stdout. Is there a more appropriate way to evaluate $# > "${curfile}"
produce() {
local curfile=$1
#Remove the first element of the list of passed arguments
shift
if [ ! -e "${curfile}" ]; then
#Run the subsequent command as shown in the list of passed arguments
echo $#
$# > "${curfile}"
fi
}
produce outputfile.vcf samtools view -bq 20 input.bam | samtools mpileup -Egu -t DP,SP -f hs37d5formatted.fa -| bcftools call -cNv -

Ok as I mentioned in my comment the issue seems to relate to the pipe characters so I had to evaluate the variable using eval and escape the pipe character. So in order to ensure the function produce interprets $# correctly I fed the command as follows. Note also that the variables are all now quoted
#######
produce() {
local curfile="$1"
#Remove the first element of the list of passed arguments
shift
if [ ! -e "${curfile}" ]; then
#Run the subsequent command as shown in the list of passed arguments
echo "$#"
eval "$# > ${curfile}"
fi
}
produce outputfile.vcf samtools view -bq 20 input.bam \| samtools mpileup -Egu -t DP,SP -f hs37d5formatted.fa -\| bcftools call -cNv -

You can use >> to append to a file. For example:
echo "line 1" >> filename
echo "line 2" >> filename
Will result in a file containing:
line 1
line 2

Related

Why is a part of the code inside a (False) if statement executed?

I wrote a small script which:
prints the content of a file (generated by another application) on paper with a matrix printer
prints the same line into a backup file
removes the original file.
The script runs every minute by a cronjob and works fine as long as there are files to print. If there are no files to print, it prints an empty line on the matrix printer and in the backup file. I don't understand why this happens as i implemented an if statement which checks if there is a file to print before the print command is executed. This behaviour only happens if the script is executed by the cron and not if i execute it manually with ./script.sh. What's the reason of this? and how can i solve it?
Something i noticed on the side is that if I place an echo "hi" command in the script, its printed to the matrix printer and the backup file. I expected that its printed to the console console when it has no >> something behind. How does this work?
The script:
#!/bin/bash
# Make sure the backup directory exists
if [ ! -d /home/user/backup_logprint ]
then
mkdir /home/user/backup_logprint
fi
# Print the records if there are any
date=`date +%Y-%m-%d`
filename='_logprint_backup'
printer_path="/dev/usb/lp0"
if [ `ls /tmp/ | grep logprint | wc -l` -gt 0 ]
then
for f in `ls /tmp | grep logprint`
do
echo `cat /tmp/$f` >> "/home/user/backup_logprint/$date$filename"
echo `cat /tmp/$f` >> $printer_path
rm "/tmp/$f"
done
fi
There's no need for ls or an if statement. Just use a proper glob in the for loop, and if no file match, the loop won't be entered.
#!/bin/bash
# Don't check first; just let mkdir decide if
# anything actually needs to be created.
d=/home/user/backup_logprint
mkdir -p "$d"
filename=$(date +"$d/%Y-%m-%d_logprint_backup")
printer_path="/dev/usb/lp0"
# Cause non-matching globs to expand to an empty
# sequence instead of being treated literally.
shopt -s nullglob
for f in /tmp/*logprint*; do
cat "$f" > "$printer_path" && mv "$f" "$d"
done

About egrep command

How can I create a bash script that admits a file as a command line argument and prints on screen all lines that have a length of more than 12 characters using egrep command?
You can use:
egrep '.{13}'
The . will match any character, and the {13} repeats it exactly 13 times. You can put this in a shell script like:
#!/bin/sh
# Make sure the user actually passed an argument. This is useful
# because otherwise grep will try and read from stdin and hang forever
if [ -z "$1" ]; then
echo "Filename needed"
exit 1
fi
egrep '.{13}' "$1"
The $1 refers to the first command argument. You can also use $2, $3, etc, and $# refers to all commandline arguments (useful if you want to run it over multiple files):
egrep '.{13}' "$#"

How to use grep with single brackets?

I was looking at an answer in another thread about which bracket pair to use with if in a bash script. [[ is less surprising and has more features such as pattern matching (=~) whereas [ and test are built-in and POSIX compliant making them portable.
Recently, I was attempting to test the result of a grep command and it was failing with [: too many arguments. I was using [. But, when I switched to [[ it worked. How would I do such a test with [ in order to maintain the portability?
This is the test that failed:
#!/bin/bash
cat > slew_pattern << EOF
g -x"$
EOF
if [ $(grep -E -f slew_pattern /etc/sysconfig/ntpd) ]; then
echo "slew mode"
else
echo "not slew mode"
fi
And the test that succeeded:
#!/bin/bash
cat > slew_pattern << EOF
g -x"$
EOF
if [[ $(grep -E -f slew_pattern /etc/sysconfig/ntpd) ]]; then
echo "slew mode"
else
echo "not slew mode"
fi
if [ $(grep -E -f slew_pattern /etc/sysconfig/ntpd) ]; then
This command will certainly fail for multiple matches. It will throw an error as the grep output is being split on line ending.
Multiple matches of grep are separated by new line and the test command becomes like:
[ match1 match2 match3 ... ]
which doesn't make much of a sense. You will get different error messages as the number of matches returned by grep (i.e the number of arguments for test command [).
For example:
2 matches will give you unary operator expected error
3 matches will give you binary operator expected error and
more than 3 matches will give you too many arguments error or such, in Bash.
You need to quote variables inside [ to prevent word splitting.
On the other hand, the Bash specific [[ prevents word splitting by default. Thus the grep output doesn't get split on new line and remains a single string which is a valid argument for the test command.
So the solution is to look only at the exit status of grep:
if grep -E -f slew_pattern /etc/sysconfig/ntpd; then
Or use quote when capturing output:
if [ "$(grep -E -f slew_pattern /etc/sysconfig/ntpd)" ]; then
Note:
You don't really need to capture the output here, simply looking at the exit status will suffice.
Additionally, you can suppress output of grep command to be printed with -q option and errors with -s option.

How to make a non interactive shell scripts [duplicate]

This question already has an answer here:
Shell script argument parsing
(1 answer)
Closed 8 years ago.
I want to make a non-interactive shell script,where I can give options at the beginning of the execution of the script.
Where I can hard-code the various actions to be taken when different inputs are provided by user.
for example:
Below should perform some action on target.txt
user#/root>myPlannedScript -p targer.txt
Below should perform some other actions on the target.txt
user#/root>myPlannedScript -a targer.txt
For example:
cat tool performs various actions when different options are given. I want my script to act like this:
:/root> cat --h
Usage: cat [OPTION] [FILE]...
Concatenate FILE(s), or standard input, to standard output.
-A, --show-all equivalent to -vET
-b, --number-nonblank number nonblank output lines
-e equivalent to -vE
-E, --show-ends display $ at end of each line
-n, --number number all output lines
-r, --reversible use \ to make the output reversible, implies -v
-s, --squeeze-blank never more than one single blank line
-t equivalent to -vT
-T, --show-tabs display TAB characters as ^I
query.sh
#!/bin/bash
if [ $# -eq 0 ]
then echo do one thing
else echo do other thing
fi
"query.sh" => do one thing
"query.sh anythingYouPut" => do other thing ;oP
but if you really want a parameter for each action
#!/bin/bash
if [ -z "$1" ]
then
echo do nothing
else
if [ $1 -eq 1 ]
then
echo do one thing
fi
if [ $1 -eq 2 ]
then
echo do other thing
fi
fi
"query.sh" => do nothing
"query.sh 1" => do one thing
"query.sh 2" => do other thing

creating Unix script to check for directories and subdirectories in

I'm completing the following for one of my assignments using Korn shell.
For each argument in the argument list (which becomes the current pathname):
Check whether the current pathname is a directory, and if so:
Initialize a variable maxsubdir with the null (empty) string, and
a maxentries variable to 0;
For each entry in the directory check if that entry represents a
directory and if so, find the numbers of entries in that
subdirectory with a pipe consisting of ls -l and wc, and save the
result in a variable named curentries.
Compare curentries with maxentries, and if curentries is greater,
update maxsubdir and maxentries. (--10 points)
When the for cycle for a directory is completed, display (with
echo) the directory name, maxsubdir and maxentries (with appropriate
explanatory text.)
If the pathname in a) is not a directory, display the pathname
and an explanatory text saying that the pathname does not represent
a directory.
Go to the next command line argument (pathname) and repeat 1-7
The execution of the script ends when all pathnames are processed (the while is completed )
This is the code I have for it so far (EDITED):
#!/bin/ksh
directoy=$1
while [ $# -ne 0 ]; do
if [ -d $1 ]; then
maxsubdir=
maxentries=0
for x in $1; do
echo "Checking if $1 represents a directory..\n"
curentries="ls -l | wc"
if [ $curentries > $maxentries ]; then
maxentries=$curentries
maxsubdir=$curentries
fi;
done
echo "The directory structure of $1 is … \n"
echo "Maximum sub directories: \n"
echo "$maxsubdir\n"
echo "Maximum directory entries: \n"
echo "$maxentries"
fi
done
Where do I need to insert the "shift" command since I Unix can only handle a limited number of arguments?
Is my syntax appropriate? Or do I have syntax errors on sort lines?
Script seems to run but does not produce output to screen? Perhaps it's endless?
Have a look here and see if this helps out. Explanations are in the code.
#!/bin/ksh
directory=$1
# check whether the entered path is a directory
if [ -d $1 ];then # yes, it's a directory
maxsubdir=null
maxentries=0
echo "$1 is a directory"
# you are only counting lines, add -l to wc
# also you have to not count the first line. it's returns the size
curentries=`ls -l $1 | wc -l`
echo ${curentries}
fi
You don't.
You do have some errors.
Or perhaps, it never reaches that code?
Your assignment says specifically to use a for loop, and you've implemented a while loop.
I'll get you started:
for directory in $*; do
cd "$directory"
curentries=$(ls -1 | wc -l)
for entry in $(ls -1); do
...
done
done

Resources