Need help formatting Tshark command string from bash script - bash

I'm attempting to run multiple parallel instances to tshark to comb through a large number of pcap files in a directory and copy the filtered contents to a new file. I'm running into an issue where Tshark is throwing an error on the command I'm feeding it.
It must have something to do with the way the command string is interpreted by tshark as I can copy / paste the formatted command string to the console and it runs just fine. I've tried formatting the command several ways and read threads from others who had similar issues. I believe I'm formatting correctly... but still get the error.
Here's what I'm working with:
Script #1: - filter
#Takes user arguments <directory> and <filter> and runs a filter on all captures for a given directory.
#
#TO DO:
#Add user prompts and data sanitization to avoid running bogus job.
#Add concatenation via mergecap /w .pcap suffix
#Delete filtered, unmerged files
#Add mtime filter for x days of logs
starttime=$(date)
if [$1 = '']; then echo "no directory specified, you must specify a directory (VLAN)"
else if [$2 = '']; then echo "no filter specified, you must specify a valid tshark filter expression"
else
echo $2 > /home/captures-user/filtered/filter-reference
find /home/captures-user/Captures/$1 -type f | xargs -P 5 -L 1 /home/captures-user/tshark-worker
rm /home/captures-user/filtered/filter-reference
fi
fi
echo Start time is $starttime
echo End time is $(date)
Script #2: - tshark-worker
# $1 = path and file name
#takes the output from the 'filter' command stored in a file and loads a local variable with it
filter=$(cat /home/captures-user/filtered/filter-reference)
#strips the directory off the current working file
file=$(sed 's/.*\///' <<< $1 )
echo $1 'is the file to run' $filter 'on.'
#runs the filter and places the filtered results in the /filtered directory
command=$"tshark -r $1 -Y '$filter' -w /home/captures-user/filtered/$file-filtered"
echo $command
$command
When I run ./filter ICE 'ip.addr == 1.1.1.1' I get the following output for each file. Note the the inclusion of == in the filter expression is not the issue, I've tried substituting 'or' and get the same output. Also, tshark is not aliased to anything, and there's no script with that name. It's the raw tshark executable in /usr/sbin.
Output:
/home/captures-user/Captures/ICE/ICE-2019-05-26_00:00:01 is the file to run ip.addr == 1.1.1.1 on.
tshark -r /home/captures-user/Captures/ICE/ICE-2019-05-26_00:00:01 -Y 'ip.addr == 1.1.1.1' -w /home/captures-user/filtered/ICE-2019-05-26_00:00:01-filtered
tshark: Display filters were specified both with "-d" and with additional command-line arguments.

As I mentioned in the comments, I think this is a problem with quoting and how your command is constructed due to spaces in the filter (and possibly in the file name and/or path).
You could try changing your tshark-worker script to something like the following:
# $1 = path and file name
#takes the output from the 'filter' command stored in a file and loads a local variable with it
filter="$(cat /home/captures-user/filtered/filter-reference)"
#strips the directory off the current working file
file="$(sed 's/.*\///' <<< $1 )"
echo $1 'is the file to run' $filter 'on.'
#runs the filter and places the filtered results in the /filtered directory
tshark -r "${1}" -Y "${filter}" -w "${file}"-filtered

Related

Bash File names will not append to file from script

Hello I am trying to get all files with Jane's name to a separate file called oldFiles.txt. In a directory called "data" I am reading from a list of file names from a file called list.txt, from which I put all the file names containing the name Jane into the files variable. Then I'm trying to test the files variable with the files in list.txt to ensure they are in the file system, then append the all the files containing jane to the oldFiles.txt file(which will be in the scripts directory), after it tests to make sure the item within the files variable passes.
#!/bin/bash
> oldFiles.txt
files= grep " jane " ../data/list.txt | cut -d' ' -f 3
if test -e ~data/$files; then
for file in $files; do
if test -e ~/scripts/$file; then
echo $file>> oldFiles.txt
else
echo "no files"
fi
done
fi
The above code gets the desired files and displays them correctly, as well as creates the oldFiles.txt file, but when I open the file after running the script I find that nothing was appended to the file. I tried changing the file assignment to a pointer instead files= grep " jane " ../data/list.txt | cut -d' ' -f 3 ---> files=$(grep " jane " ../data/list.txt) to see if that would help by just capturing raw data to write to file, but then the error comes up "too many arguments on line 5" which is the 1st if test statement. The only way I get the script to work semi-properly is when I do ./findJane.sh > oldFiles.txt on the shell command line, which is me essentially manually creating the file. How would I go about this so that I create oldFiles.txt and append to the oldFiles.txt all within the script?
The biggest problem you have is matching names like "jane" or "Jane's", etc. while not matching "Janes". grep provides the options -i (case insensitive match) and -w (whole-word match) which can tailor your search to what you appear to want without having to use the kludge (" jane ") of appending spaces before an after your search term. (to properly do that you would use [[:space:]]jane[[:space:]])
You also have the problem of what is your "script dir" if you call your script from a directory other than the one containing your script, such as calling your script from your $HOME directory with bash script/findJane.sh. In that case your script will attempt to append to $HOME/oldFiles.txt. The positional parameter $0 always contains the full pathname to the current script being run, so you can capture the script directory no matter where you call the script from with:
dirname "$0"
You are using bash, so store all the filenames resulting from your grep command in an array, not some general variable (especially since your use of " jane " suggests that your filenames contain whitespace)
You can make your script much more flexible if you take the information of your input file (e.g list.txt), the term to search for (e.g. "jane"), the location where to check for existence of the files (e.g. $HOME/data) and the output filename to append the names to (e.g. "oldFile.txt") as command line [positonal] parameters. You can give each default values so it behaves as you currently desire without providing any arguments.
Even with the additional scripting flexibility of taking the command line arguments, the script actually has fewer lines simply filling an array using mapfile (synonymous with readarray) and then looping over the contents of the array. You also avoid the additional subshell for dirname with a simple parameter expansion and test whether the path component is empty -- to replace with '.', up to you.
If I've understood your goal correctly, you can put all the pieces together with:
#!/bin/bash
# positional parameters
src="${1:-../data/list.txt}" # 1st param - input (default: ../data/list.txt)
term="${2:-jane}" # 2nd param - search term (default: jane)
data="${3:-$HOME/data}" # 3rd param - file location (defaut: ../data)
outfn="${4:-oldFiles.txt}" # 4th param - output (default: oldFiles.txt)
# save the path to the current script in script
script="$(dirname "$0")"
# if outfn not given, prepend path to script to outfn to output
# in script directory (if script called from elsewhere)
[ -z "$4" ] && outfn="$script/$outfn"
# split names w/term into array
# using the -iw option for case-insensitive whole-word match
mapfile -t files < <(grep -iw "$term" "$src" | cut -d' ' -f 3)
# loop over files array
for ((i=0; i<${#files[#]}; i++)); do
# test existence of file in data directory, redirect name to outfn
[ -e "$data/${files[i]}" ] && printf "%s\n" "${files[i]}" >> "$outfn"
done
(note: test expression and [ expression ] are synonymous, use what you like, though you may find [ expression ] a bit more readable)
(further note: "Janes" being plural is not considered the same as the singular -- adjust the grep expression as desired)
Example Use/Output
As was pointed out in the comment, without a sample of your input file, we cannot provide an exact test to confirm your desired behavior.
Let me know if you have questions.
As far as I can tell, this is what you're going for. This is totally a community effort based on the comments, catching your bugs. Obviously credit to Mark and Jetchisel for finding most of the issues. Notable changes:
Fixed $files to use command substitution
Fixed path to data/$file, assuming you have a directory at ~/data full of files
Fixed the test to not test for a string of files, but just the single file (also using -f to make sure it's a regular file)
Using double brackets — you could also use double quotes instead, but you explicitly have a Bash shebang so there's no harm in using Bash syntax
Adding a second message about not matching files, because there are two possible cases there; you may need to adapt depending on the output you're looking for
Removed the initial empty redirection — if you need to ensure that the file is clear before the rest of the script, then it should be added back, but if not, it's not doing any useful work
Changed the shebang to make sure you're using the user's preferred Bash, and added set -e because you should always add set -e
#!/usr/bin/env bash
set -e
files=$(grep " jane " ../data/list.txt | cut -d' ' -f 3)
for file in $files; do
if [[ -f $HOME/data/$file ]]; then
if [[ -f $HOME/scripts/$file ]]; then
echo "$file" >> oldFiles.txt
else
echo "no matching file"
fi
else
echo "no files"
fi
done

Making A GNU-Grep-Based Script MacOS Friendly

I recently discovered that mutt allows me to do something I've been trying, without success, to do in my GUI e-mail client for years: (largely) automate the process of saving an e-mail message (*.eml) to a local directory of my choice.
This Unix & Linux StackExchange post shared a rough-and-ready mutt macro for handling this process. As you'll see, however, the macro's grep commands reach for the -P flag (i.e. Perl regular expressions) and, thus, do not run on the Macbook I'm currently using:
#!/usr/bin/env zsh
#Saved piped email to "$1/YYMMDD SUBJECT.eml"
# Don't overwrite existing file
set -o noclobber
message=$(cat)
mail_date=$(<<<"$message" grep -oPm 1 '^Date: ?\K.*')
formatted_date=$(date -d"$mail_date" +%y%m%d)
# Get the first line of the subject, and change / to ∕ so it's not a subdirectory
subject=$(<<<"$message" grep -oPm 1 '^Subject: ?\K.*' | sed 's,/,∕,g')
if [[ $formatted_date == '' ]]; then
echo Error: no date parsed
exit 1
elif [[ $subject == '' ]]; then
echo Warning: no subject found
fi
echo "${message}" > "$1/$formatted_date $subject.eml" && echo Email saved to "$1/$formatted_date $subject.eml"
I'm far from comfortable with complex grep queries, so my meager efforts to make this script work (e.g. swapping out the -P flag for the -e flag) have met with failure.
To wit, this is the error message thrown when I swap in the -e flag:
grep: 1: No such file or directory
grep: ^Date: ?\K.*: No such file or directory
usage: date [-jnRu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ...
[-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]
grep: 1: No such file or directory
grep: ^Subject: ?\K.*: No such file or directory
Error: no date parsed
Press any key to continue...
Mercifully, the error messages here are pretty clear. The script's use of 1 appears to be faulty, as does the last bit of the anchored grep query (e.g. ^Date: ?\K.*).
Unfortunately, I have no idea how to begin to resolve these errors.
What I'm attempting to do is, in fact, quite simple. Rather than manually running | cat > FILE_PATH/email.eml I'd like to simply be able to hit a key in mutt, extract the selected e-mail's date (e.g. everything to the end-of-the-line after Date:) and subject (e.g. everything to the end-of-line after Subject), then use that information to generate the name of the *.eml file saved locally (e.g. YYYY-MM-DD subject.eml).
Does anyone have any suggestions on how to make this script play nice in MacOS?
One option is to use zsh parameter expansions to parse the values, so there's no need to worry about grep versions. As a bonus, this launches fewer subprocesses:
#!/usr/bin/env zsh
# Save piped email to "$1/YYMMDD SUBJECT.eml"
# Don't overwrite existing file
set -o noclobber
# stdin split into array of lines:
message=("${(#f)$(<&0)}")
mail_date=${${(M)message:#Date: *}[1]#Date: }
formatted_date=$(gdate -d"$mail_date" +%y%m%d)
# Get the subject, and change '/' to '-'
subject=${${${(M)message:#Subject: *}[1]#Subject: }//\//-}
if [[ -z $formatted_date ]]; then
print -u2 Error: no date parsed
exit 1
elif [[ -z $subject ]]; then
print -u2 Warning: no subject found
fi
outdir=${1:?Output directory must be specified}
if [[ ! -d $outdir ]]; then
print -u2 Error: no output directory $outdir
exit 1
fi
outfile="$outdir/$formatted_date $subject.eml"
print -l $message > "$outfile" && print Email saved to "$outfile"
This statement gets the date from the array of lines in message:
mail_date=${${(M)message:#Date: *}[1]#Date: }
${(M)...:#...} - gets elements that match a pattern from the array. Here we use it to find elements that start with Date: .
${...[1]} - returns the first match.
${...#Date: } - removes the prefix Date: , leaving the date string.
This similar statement has an additional expansion that replaces all instances of / with -:
subject=${${${(M)message:#Subject: *}[1]#Subject: }//\//-}
The parameter expansions are documented in the zshexpn man page.
PS: trailing newlines will be removed from the message written to the file. This is a difficult-to-avoid consequence of using a command substitution like $(...). It's not likely to be a significant issue.

Loop for all files with certain name in directory in bash

I'm trying to make a script that would test whether my file is exactly as it should be, but I haven't been using bash before:
#!/bin/bash
./myfile <test.in 1>>test.out 2>>testerror.out
if cmp -s "test.out" "pattern.out"
then
echo "Test matches pattern"
else
echo "Test does not match pattern"
fi
if cmp -s "testerror.out" "pattern.err"
then
echo "Errors matches pattern"
else
echo "Errors does not match pattern"
fi
Can I write it in such way that after calling ./script.sh myfile pattern my scripts would run over all files named pattern*.in and check if myfile gives same files as pattern*.out and pattern*.err ? e.g there are files pattern1, pattern2, pattern4 and i want to run test for them, but not for pattern3 that doesn't exist.
Can I somehow go around creating new files? (Assuming i don't need them) If I were doing it from command line, I'd go with something like
< pattern.in ./myfile | diff -s ./pattern.out
but I have no idea how to write it in script file to make it work.
Or maybe i should just use rm everytime?
If I understand you correctly:
for infile in pattern*.in ; do
outfile="${infile%.in}.out"
errfile="${infile%.in}.err"
echo "Working on input $infile with output $outfile and error $errfile"
./myfile <"$infile" >>"$outfile" 2>>"$errfile"
# Your `if`..`fi` blocks here, referencing infile/outfile/errfile
done
The % replacement operator strips a substring off the end of a variable's value. So if $infile is pattern.in, ${infile%.in} is that without the trailing .in, i.e., pattern. The outfile and errfile assignments use this to copy the first part (e.g., pattern1) of the particular .in file being processed.

no such file or directory error when using variables (works otherwise)

I am new to programming and just starting in bash.
I'm trying to print a list of directories and files to a txt file, and remove some of the path that gets printed to make it cleaner.
It works with this:
TODAY=$(date +"%Y-%m-%d")
cd
cd Downloads
ls -R ~/Music/iTunes/iTunes\ Media/Music | sed 's/\/Users\/BilPaLo\/Music\/iTunes\/iTunes\ Media\/Music\///g' > music-list-$TODAY.txt
But to clean it up I want to use variables like so,
# Creates a string of the date, format YYYY-MM-DD
TODAY="$(date +"%Y-%m-%d")"
# Where my music folders are
MUSIC="$HOME/Music/iTunes/iTunes\ Media/Music/"
# Where I want it to go
DESTINATION="$HOME/Downloads/music-list-"$TODAY".txt"
# Path name to be removed from text file
REMOVED="\/Users\/BilPaLo\/Music\/iTunes\/iTunes\ Media\/Music\/"
ls -R "$MUSIC" > "$DESTINATION"
sed "s/$REMOVED//g" > "$DESTINATION"
but it gives me a 'no such file or directory' error that I can't seem to get around.
I'm sure there are many other problems with this code but this one I don't understand.
Thank you everyone! I followed the much needed formatting advice and #amo-ej1's answer and now this works:
# Creates a string of the date format YYYY-MM-DD
today="$(date +"%Y-%m-%d")"
# Where my music folders are
music="$HOME/Music/iTunes/iTunes Media/Music/"
# Where I want it to go
destination="$HOME/Downloads/music-list-$today.txt"
# Temporary file
temp="$HOME/Downloads/temp.txt"
# Path name to be removed of text file to only leave artist name and album
remove="\\/Users\\/BilPaLo\\/Music\\/iTunes\\/iTunes\\ Media\\/Music\\/"
# lists all children of music and writes it in temp
ls -R "$music" > "$temp"
# substitutes remove by nothing and writes it in destination
sed "s/$remove//g" "$temp" > "$destination"
rm $temp #deletes temp
First when debugging bash it can be helpful to start bash with the -x flags (bash -x script.sh) or within the script enter set -x, that way bash will print out the commands it is executing (with the variable expansions) and you can more easily spot errors that way.
In this specific snippet our ls output is being redirected to a file called $DESTINATION and and sed will read from standard input and write also to $DESTINATION. So however you wanted to replace the pipe in your oneliner is wrong. As a result this will look as if your program is blocked but sed will simply wait for input arriving on standard input.
As for the 'no such file or directory', try executing with set -x and doublecheck the paths it is trying to access.

Investigating a diff error in a bash script when variables are used instead of hardcoded file names

I have a script that looks for files of specific type in a specified directory and if they are present, generates a file with the basenames before creating a tar.gz. Once compressed, I check to ensure the tarball contains all the files by running a diff check.
I have created a pair of variables that are the pre-compressed file list and those found in the tarball. When I run an if statement including diff of the variables, I receive this error:
diff: missing operand after `/my/original/dir/filelist.txt'
diff: Try `diff --help' for more information.
I worked around this by referencing the files themselves rather than the created variables. If I run the if statement in a separate bash script, it works just fine using the variables so I am entirely lost as to what my error is in my larger script. Below I provide both the snippet from the large script and the diff statement as its own script for reference.
The if diff in its own script:
#!/bin/sh
filelist=(filelist.txt)
tarfiles=(tarfiles.txt)
#differences=$(diff filelist.txt tarfiles.txt) #Uncomment if below fails
differences=$(diff $filelist $tarfiles)
if $differences > /dev/null ; then
echo Same
else
echo Different
fi
The above works just fine.
Now including this at the end my larger script:
TARFILES=$(tar -tzf "$ARCHIVES/tarredfiles.tar.gz" | awk -F/ '{ if($NF != "") print $NF }' > $LOGS/tarfiles.txt)
FILELIST=($LOGS/filelist.txt)
#Check to see if it all worked
DIFF=$(diff $FILELIST $TARFILES)
cd $LOGS #I shouldn't need to do this but I do as a safety mechanism
#if diff filelist.txt tarfiles.txt > /dev/null ; then
if diff $FILELIST $TARFILES > /dev/null ; then
echo "Today's files have been archived and checked."
else
echo "Some or none of today's files have been archived, check the logs to find the error."
echo (diff $TARFILES $FILELIST) > $LOGS/$(date '+%Y%m%d')errors.txt
fi
I have tried enclosing the variables in "" and it didn't seem to make a difference.
The way you populate TARFILES results in it being empty. What is it that you're trying to store in the variable?
This line
TARFILES=$(tar -tzf "$ARCHIVES/tarredfiles.tar.gz" | awk -F/ '{ if($NF != "") print $NF }' > $LOGS/tarfiles.txt)
Does the following steps
Extracts a list of the filenames (-t) from the compressed (-z) tar file (-f) named tarredfiles.tar.gz in the directory referred to by the $ARCHIVES variables
Sends (pipes) that list of filenames into awk where you print the last component of the filename, that is the last field ($NF) of each line when it is split by / (-F/)
Sends (redirects) all of that output into the log file $LOGS/tarfiles.txt
Captures any other output (of which there will be none!) and stores it in the TARFILES variable.
So, the variable TARFILES is always empty, but the file tarfiles.txt has content in it.
It seems that you want the diff to compare the content of tarfiles.txt with the content of filelist.txt, but you're trying to use your variables in a way that isn't really compatible with that.
An expression of the form:
TARFILES=$( command goes here )
captures the output of that command.
And
TARFILES=$( command goes here > some-file.txt )
sends the output of the command into the file, and then captures nothing.
What you want is something like:
TARFILES=some-file.txt
command goes here > $TARFILES
which will set the variable to be the name of your file, and then run a command which put content into that file.
So, specifically:
TARFILES=$LOGS/tarfiles.txt
tar -tzf "$ARCHIVES/tarredfiles.tar.gz" | awk -F/ '{ if($NF != "") print $NF }' > $TARFILES
When working will shell scripts, it is very common to be running commands that produce output that goes into files, etc. One thing you need to be really clear about in the logic of your script is when you want your variables to contain actual content (that is, the output of a command), and when you want them to contain filenames.
In your case you want to run diff on 2 files ("tarfiles" and "filelist") that happen to contain a list of filenames, so that means there's a little bit more to keep track of, but essentially you want to populate "tarfiles" with the output from a command, and then run a diff where you pass in the 2 files names "tarfiles" and "filelist". So you never want to use $( ... ) to populate tarfiles.txt because that is how you capture the output of a command into a variable, and what you're trying to do is store a filename in your variable.

Resources