Pass the argument to if condition used in pipe - bash

I am trying to write a script which extracts data from the file "nohup.out" using tail -f and executing dig command on condition.
#!/bin/bash
nohup proxychains firefox
tail -f nohup.out | xargs if [[ {} == *"denied"* ]]
then
dig -x `cut -d '-' -f 6 {} | cut -d ':' -f 1`&;
fi
Output of nohup.out is
|S-chain|-<>-10.1.1.16:80-<><>-93.184.220.29:80-<--denied
|S-chain|-<>-10.1.1.16:80-<><>-93.184.220.29:80-<--denied
|S-chain|-<>-10.1.1.16:80-<><>-216.58.209.77:443-<><>-OK
|S-chain|-<>-10.1.1.16:80-<><>-46.28.247.89:443-<><>-OK
With the below command I am able to extract the IP for reverse DNS lookup.
cut -d '-' -f 6 | cut -d ':' -f 1
I am not able to find a way to pass the argument to cut and if command.

What you need is to convert the if statement into an argument to bash. Doing a simplistic transform, assuming that the code in the question has a chance of working, you get:
tail -f nohup.out |
xargs -I '{}' bash -c "if [[ {} == *"denied"* ]]; then dig -x $(cut -d '-' -f 6 {} | cut -d ':' -f 1) & fi"
This is exactly the same basic treatment as was needed for a for loop being executed by nohup — you need a shell to run the built-in command. See Why can't I use Unix nohup with Bash for loop? for an exactly analogous situation.
However, on further reflection, you want to cut the string which is the IP address, not the file with that as a name, so the command needs to echo the string into the cut commands. You also have to tear your hair getting the sub-commands executed correctly; you need a backslash before the $ of $(…), or before each of the back-ticks if you insist on using `…` notation, as well as using backslash-double-quote to protect the angle-brackets in the string.
tail -f nohup.out |
xargs -I '{}' bash -c "if [[ '{}' != *denied* ]]; then echo dig -x \"\$(echo '{}' | cut -d '-' -f 6 | cut -d ':' -f 1)\" & fi"
Now we need to debate the use of the condition and two cut commands (and the general hair loss). You could use:
tail -f nohup.out |
grep -v denied |
xargs -I '{}' bash -c "echo dig -x \$(echo '{}' | cut -d '-' -f 6 | cut -d ':' -f 1) &"
or, more sensibly:
tail -f nohup.out |
awk -F '[-:]' '/denied/ { next } { print "dig -x " $7 " &" }' |
sh -x
or any of a myriad other ways to do it.

awk -F- '!/denied/ {print $6}'
splits each input line in fields separated by -, ignores the lines matching denied and extracts the 6th field of the remaining lines. With you example it outputs:
216.58.209.77:443
46.28.247.89:443

Related

Bash - how do i output line and then pipe line to another command side by side? [duplicate]

cat a.txt | xargs -I % echo %
In the example above, xargs takes echo % as the command argument. But in some cases, I need multiple commands to process the argument instead of one. For example:
cat a.txt | xargs -I % {command1; command2; ... }
But xargs doesn't accept this form. One solution I know is that I can define a function to wrap the commands, but I want to avoid that because it is complex. Is there a better solution?
cat a.txt | xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _
...or, without a Useless Use Of cat:
<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _
To explain some of the finer points:
The use of "$arg" instead of % (and the absence of -I in the xargs command line) is for security reasons: Passing data on sh's command-line argument list instead of substituting it into code prevents content that data might contain (such as $(rm -rf ~), to take a particularly malicious example) from being executed as code.
Similarly, the use of -d $'\n' is a GNU extension which causes xargs to treat each line of the input file as a separate data item. Either this or -0 (which expects NULs instead of newlines) is necessary to prevent xargs from trying to apply shell-like (but not quite shell-compatible) parsing to the stream it reads. (If you don't have GNU xargs, you can use tr '\n' '\0' <a.txt | xargs -0 ... to get line-oriented reading without -d).
The _ is a placeholder for $0, such that other data values added by xargs become $1 and onward, which happens to be the default set of values a for loop iterates over.
You can use
cat file.txt | xargs -i sh -c 'command {} | command2 {} && command3 {}'
{} = variable for each line on the text file
With GNU Parallel you can do:
cat a.txt | parallel 'command1 {}; command2 {}; ...; '
For security reasons it is recommended you use your package manager to
install. But if you cannot do that then you can use this 10 seconds
installation.
The 10 seconds installation will try to do a full installation; if
that fails, a personal installation; if that fails, a minimal
installation.
$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 883c667e01eed62f975ad28b6d50e22a
12345678 883c667e 01eed62f 975ad28b 6d50e22a
$ md5sum install.sh | grep cc21b4c943fd03e93ae1ae49e28573c0
cc21b4c9 43fd03e9 3ae1ae49 e28573c0
$ sha512sum install.sh | grep da012ec113b49a54e705f86d51e784ebced224fdf
79945d9d 250b42a4 2067bb00 99da012e c113b49a 54e705f8 6d51e784 ebced224
fdff3f52 ca588d64 e75f6033 61bd543f d631f592 2f87ceb2 ab034149 6df84a35
$ bash install.sh
I prefer style which allows dry run mode (without | sh) :
cat a.txt | xargs -I % echo "command1; command2; ... " | sh
Works with pipes too:
cat a.txt | xargs -I % echo "echo % | cat " | sh
This is just another approach without xargs nor cat:
while read stuff; do
command1 "$stuff"
command2 "$stuff"
...
done < a.txt
This seems to be the safest version.
tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'command1 "$#"; command2 "$#";' ''
(-0 can be removed and the tr replaced with a redirect (or the file can be replaced with a null separated file instead). It is mainly in there since I mainly use xargs with find with -print0 output) (This might also be relevant on xargs versions without the -0 extension)
It is safe, since args will pass the parameters to the shell as an array when executing it. The shell (at least bash) would then pass them as an unaltered array to the other processes when all are obtained using ["$#"][1]
If you use ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', the assignment will fail if the string contains double quotes. This is true for every variant using -i or -I. (Due to it being replaced into a string, you can always inject commands by inserting unexpected characters (like quotes, backticks or dollar signs) into the input data)
If the commands can only take one parameter at a time:
tr '[\n]' '[\0]' < a.txt | xargs -r0 -n1 /bin/bash -c 'command1 "$#"; command2 "$#";' ''
Or with somewhat less processes:
tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'for f in "$#"; do command1 "$f"; command2 "$f"; done;' ''
If you have GNU xargs or another with the -P extension and you want to run 32 processes in parallel, each with not more than 10 parameters for each command:
tr '[\n]' '[\0]' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c 'command1 "$#"; command2 "$#";' ''
This should be robust against any special characters in the input. (If the input is null separated.) The tr version will get some invalid input if some of the lines contain newlines, but that is unavoidable with a newline separated file.
The blank first parameter for bash -c is due to this: (From the bash man page) (Thanks #clacke)
-c If the -c option is present, then commands are read from the first non-option argument com‐
mand_string. If there are arguments after the command_string, the first argument is assigned to $0
and any remaining arguments are assigned to the positional parameters. The assignment to $0 sets
the name of the shell, which is used in warning and error messages.
One thing I do is to add to .bashrc/.profile this function:
function each() {
while read line; do
for f in "$#"; do
$f $line
done
done
}
then you can do things like
... | each command1 command2 "command3 has spaces"
which is less verbose than xargs or -exec. You could also modify the function to insert the value from the read at an arbitrary location in the commands to each, if you needed that behavior also.
Another possible solution that works for me is something like -
cat a.txt | xargs bash -c 'command1 $#; command2 $#' bash
Note the 'bash' at the end - I assume it is passed as argv[0] to bash. Without it in this syntax the first parameter to each command is lost. It may be any word.
Example:
cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $#; echo "data again: " $#' bash
My current BKM for this is
... | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'
It is unfortunate that this uses perl, which is less likely to be installed than bash; but it handles more input that the accepted answer. (I welcome a ubiquitous version that does not rely on perl.)
#KeithThompson's suggestion of
... | xargs -I % sh -c 'command1; command2; ...'
is great - unless you have the shell comment character # in your input, in which case part of the first command and all of the second command will be truncated.
Hashes # can be quite common, if the input is derived from a filesystem listing, such as ls or find, and your editor creates temporary files with # in their name.
Example of the problem:
$ bash 1366 $> /bin/ls | cat
#Makefile#
#README#
Makefile
README
Oops, here is the problem:
$ bash 1367 $> ls | xargs -n1 -I % sh -i -c 'echo 1 %; echo 2 %'
1
1
1
1 Makefile
2 Makefile
1 README
2 README
Ahh, that's better:
$ bash 1368 $> ls | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'
1 #Makefile#
2 #Makefile#
1 #README#
2 #README#
1 Makefile
2 Makefile
1 README
2 README
$ bash 1369 $>
Try this:
git config --global alias.all '!f() { find . -d -name ".git" | sed s/\\/\.git//g | xargs -P10 -I{} git --git-dir={}/.git --work-tree={} $1; }; f'
It runs ten threads in parallel and does what ever git command you want to all repos in the folder structure. No matter if the repo is one or n levels deep.
E.g: git all pull
I have good idea to solve the problem.
Only write a comman mcmd, then you can do
find . -type f | xargs -i mcmd echo {} ## cat {} #pipe sed -n '1,3p'
The mcmd content as follows:
echo $* | sed -e 's/##/\n/g' -e 's/#pipe/|/g' | csh

Bash script echos but won't execute

I'm creating a simple bash script to remove all Bluetooth devices from my system. What it does is use bt-device -l to list the bluetooth devices, then grabs the MAC address in between the parens, then calls bt-device -r <MACAddress> to remove the device. I'm not that great at bash scripts but when I replace the bt-device call with echo, I get the correct output. When I put the command back in it says the device wasn't found. If I manually make the call it works (outputs "Done").
Sample output of the bt-device -l command:
Added Devices:
Samico BP (12:34:56:78:9a:bc)
SensorTag 2.0 (12:34:56:78:9a:bd)
And the script I'm using:
#!/usr/bin/env bash
bt-device -l | sed 1d |
while read x; do
bt-device -r $x | cut -d "(" -f2 | cut -d ")" -f1
done
When I run it, it runs the bt-device -r command but the output is Error: Device not found as if I'd typed the MAC address wrong. If I replace the bt-device call in the script with echo, I get the list of MAC addresses as expected.
Updated script working
#!/usr/bin/env bash
bt-device -l | sed 1d |
while read x; do
bt-device -r $(echo $x | cut -d "(" -f2 | cut -d ")" -f1)
done
The problem is that you're running cut -d "(" -f2 | cut -d ")" -f1 on the output of bt-device -r, instead of on its argument.
Instead, you should write something like this:
bt-device -l | sed 1d | cut -d "(" -f2 | cut -d ")" -f1 |
while read x; do
bt-device -r $x
done
You'd want to process $x prior to using it:
bt-device -l | sed 1d |
while read x; do
dev=$( echo "$x" | cut -d "(" -f2 | cut -d ")" -f1 )
bt-device -r "$dev"
done

xargs not working with built in shell functions

I am trying to speed up the processing of a database. I migrated towards xargs. But I'm seriously stuck. Piping a list of arguments to xargs does not work if the command invoked by xargs isn't a built in. I can't figure out why. Here is my code:
#!/bin/bash
list='foo
bar'
test(){
echo "$1"
}
echo "$list" | tr '\012' '\000' | xargs -0 -n1 -I '{}' 'test' {}
So there is no output at all. And test function never gets executed. But if I replace "test" in the "xargs" command with "echo" or "printf" it works fine.
You can't pass a shell function to xargs directly, but you can invoke a shell.
printf 'foo\0bar\0' |
xargs -r -0 sh -c 'for f; do echo "$f"; done' _
The stuff inside sh -c '...' can be arbitrarily complex; if you really wanted to, you could declare and then use your function. But since it's simple and nonrecursive, I just inlined the functionality.
The dummy underscore parameter is because the first argument after sh -c 'script' is used to populate $0.
Because your question seems to be about optimization, I imagine you don't want to spawn a separate shell for every item passed to xargs -- if you did, nothing would get faster. So I put in the for loop and took out the -I etc arguments to xargs.
xargs takes an executable as an argument (including custom scripts) rather than a function defined in the environment.
Either move your code to a script or use xargs to pass arguments to an external command.
Change from:
echo "$list" | tr '\012' '\000' | xargs -0 -n1 -I '{}' 'test' {}
To:
export -f test
echo "$list" | tr '\012' '\000' | xargs -0 -n1 -I '{}' sh -c 'test {}'
I've seen a solution from 'jac' on the bbs.archlinux.org web-site that uses a primary and secondary (slave) pair of scripts that are very efficients. Instead of an internal 'function' that normally would accept a single $1 parameter, the primary sends a list of parameters to its secondary where a while-loop handles each member of the list as consecutive $1 values. Here's a sample pair I'm using to apply the 'file' command to a bunch of executables, which in my case all begin with "em" in the filename. Make changes as necessary:
#!/bin/bash
# primary: showfil
ls -l em* | grep '^-rwx' | awk '{$1=$2=$3=$4=$5=$6=$7=$8=""; print $0}' | xargs -I% ~/showfilf "%"
~/showfilf fixmstr spisort trc
exit 0
#!/bin/bash
# secondary: showfilf
myarch=$(uname -s | grep 'arwin')
while [[ -n "$1" ]]; do
if [ -x "$1" ]; then
if [ -n "$myarch" ]; then
file "./$1"
else
myfile=$(file "./$1" | awk '{print $1" "$3" "$10" "$11" "$12}')
myfile=${myfile%(uses}
myfile=${myfile%for}
echo "$myfile"
fi
fi
shift
done
exit 0
This code works on Darwin (Mac) and Linux, and probably other systems. The 'grep' in the primary retains only executable files, not directories or symlinks. The 'awk' eliminates the first eight fields of 'ls' and retains just the filename,which is passed to 'xargs', which builds a list of quoted filenames to send to 'showfilf'. There's a separate invocation of 'showfilf' with three other filenames in the list. 'showfilf' has a while-loop which processes the list. Note that there is system-dependent code here, determined by 'uname -s' and 'grep'. Lastly, make these scripts executable, and place them on your $PATH, such as $HOME. If your $PATH doesn't include your $HOME, I recommend you modify it in your .bashrc or .bash_login something like this: export PATH=$PATH:$HOME

simple bash script not terminating

I have very simple bash script:
#!/bin/bash
echo -n "A: ";
grep -v ">" | grep -o "A" $1 | wc -l;
I type
./script.sh 1.fasta
I got
A: 131
But the curcor is still blicking and my script is not finishing. What's wrong here?
Thank you.
This is the problem command:
grep -v ">" | grep -o "A" $1 | wc -l;
Since first command grep -v ">" is waiting for the input from STDIN as you haven't supplied any file to be searched by grep.
PS: Even grep -o "A" $1 is also problem since piped command will take input from output of the previous command in chain.
Probably you meant:
grep -v ">" "$1" | grep -o "A" | wc -l
Your first grep does not have a file argument so it will read from standard input:
grep -v ">" | grep -o "A" $1 | wc -l;
(read stdin) (read $1)
The reason why you get the 131 is because your second grep does have a file argument so it's getting all lines in $1 that have an A. However it's still waiting around for the end of the first grep to finish (which you can do with CTRL-D).
What you probably wanted to do is this:
grep -v ">" "$1" | grep -o "A" | wc -l
This will find all lines in $1 without a >, then all occurrences of A in that, counting them.

How to use tail in combination with sed .

I want to beep a Sound , incase there is any Exception ocured in Log Files .
I am using bash script .
But unfortunately when tail is used in combintaion with sed , it doesn't work .
I have tried with the below commands and posting here .
tail -f mylogs.log | grep "Exception" | sed -e $'s/Exception/Exception\a/'
tail -f mylogs.log | sed -e $'s/Exception/Exception\a/'
tail -f mylogs.log | grep "Exception" | sed -e $'s/Exception/Exception\a/'
The problem is that grep sees that it's not writing to the terminal, so it buffers its output, eventually writing big chunks that sed can process all at once. To tell it to print out lines as soon as they're available, use the --line-buffered option:
tail -f mylogs.log \
| grep --line-buffered Exception \
| sed -u -e $'s/Exception/Exception\a/'
(Note that I've also added the -u flag to sed, which is similar to grep's --line-buffered option. In my testing it didn't seem to make a difference for this command, but I figure it's better to include it just in case.)

Resources