bash - How to find string from file and get its position? - string

File services - contains many records like this one:
define service {
host_name\t\t\t\tHOSTNAME
...
...
}
File hosts - contains records:
define host {
host_name\t\t\t\tHOSTNAME
...
...
}
and I need to go to hosts, somehow get name of HOSTNAME from first record, then go to file services and find all records with that HOSTNAME and put them to other file. Then do it for every HOSTNAME in hosts.
What I don't know is primary how to get the HOSTNAME from file hosts and then how to get a whole record in file services to a variable. I have prepared a regex (hope it's right) ^define.*host_name\t\t\t\t$HOSTNAME.*}
Please give me a few advices or examples how to get wanted result.

The files you provide look very much like nagios configuration files.
sed might be your friend here, as it allows you to slice the file into smaller parts, eg:
:t
/^define service {/,/}$/ { # For each line between these block markers..
/}$/!{ # If we are not at the /end/ marker
$!{ # nor the last line of the file,
N; # add the Next line to the pattern space
bt
} # branch (loop back) to the :t label.
} # This line matches the /end/ marker.
/host_name[ \t]\+HOSTNAME\b/!d; # delete the block if wrong host.
}
That example lifted from the sed faq 4.21, and adapted slightly. You could also look at question 4.22 which appears to address this directly:
http://sed.sourceforge.net/sedfaq4.html#s4.22
Like the previous answer, I'm also inclined to say you're probably better off using another scripting language. If you need a different interpreter to get this done anyway, might as well use something you know.

This task a bit too complex for a bash script. I would use Perl:
#!/usr/bin/perl
use warnings;
use strict;
open my $SRV, '<', 'services' or die $!;
open my $HST, '<', 'hosts' or die $!;
my %services;
{ local $/ = "\n}";
while (my $service = <$SRV>) {
my ($hostname) = $service =~ /^\s*host_name\t+(.+?)\s*$/m;
push #{ $services{$hostname} }, $service if defined $hostname;
}
}
while (my $line = <$HST>) {
if (my ($host) = $line =~ /^\s*host_name\t+(.+?)\s*$/) {
if (exists $services{$host}) {
print "===== $host =====\n";
print "$_\n" for #{ $services{$host} };
} else {
warn "$host not found in services!\n";
}
}
}

Related

finding a file in directory using perl script

I'm trying to develop a perl script that looks through all of the user's directories for a particular file name without the user having to specify the entire pathname to the file.
For example, let's say the file of interest was data.list. It's located in /home/path/directory/project/userabc/data.list. At the command line, normally the user would have to specify the pathname to the file like in order to access it, like so:
cd /home/path/directory/project/userabc/data.list
Instead, I want the user just to have to enter script.pl ABC in the command line, then the Perl script will automatically run and retrieve the information in the data.list. which in my case, is count the number of lines and upload it using curl. the rest is done, just the part where it can automatically locate the file
Even though very feasible in Perl, this looks more appropriate in Bash:
#!/bin/bash
filename=$(find ~ -name "$1" )
wc -l "$filename"
curl .......
The main issue would of course be if you have multiple files data1, say for example /home/user/dir1/data1 and /home/user/dir2/data1. You will need a way to handle that. And how you handle it would depend on your specific situation.
In Perl that would be much more complicated:
#! /usr/bin/perl -w
eval 'exec /usr/bin/perl -S $0 ${1+"$#"}'
if 0; #$running_under_some_shell
use strict;
# Import the module File::Find, which will do all the real work
use File::Find ();
# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.
# for the convenience of &wanted calls, including -eval statements:
# Here, we "import" specific variables from the File::Find module
# The purpose is to be able to just type '$name' instead of the
# complete '$File::Find::name'.
use vars qw/*name *dir *prune/;
*name = *File::Find::name;
*dir = *File::Find::dir;
*prune = *File::Find::prune;
# We declare the sub here; the content of the sub will be created later.
sub wanted;
# This is a simple way to get the first argument. There is no
# checking on validity.
our $filename=$ARGV[0];
# Traverse desired filesystem. /home is the top-directory where we
# start our seach. The sub wanted will be executed for every file
# we find
File::Find::find({wanted => \&wanted}, '/home');
exit;
sub wanted {
# Check if the file is our desired filename
if ( /^$filename\z/) {
# Open the file, read it and count its lines
my $lines=0;
open(my $F,'<',$name) or die "Cannot open $name";
while (<$F>){ $lines++; }
print("$name: $lines\n");
# Your curl command here
}
}
You will need to look at the argument-parsing, for which I simply used $ARGV[0] and I do dont know what your curl looks like.
A more simple (though not recommended) way would be to abuse Perl as a sort of shell:
#!/usr/bin/perl
#
my $fn=`find /home -name '$ARGV[0]'`;
chomp $fn;
my $wc=`wc -l '$fn'`;
print "$wc\n";
system ("your curl command");
Following code snippet demonstrates one of many ways to achieve desired result.
The code takes one parameter, a word to look for in all subdirectories inside file(s) data.list. And prints out a list of found files in a terminal.
The code utilizes subroutine lookup($dir,$filename,$search) which calls itself recursively once it come across a subdirectory.
The search starts from current working directory (in question was not specified a directory as start point).
use strict;
use warnings;
use feature 'say';
my $search = shift || die "Specify what look for";
my $fname = 'data.list';
my $found = lookup('.',$fname,$search);
if( #$found ) {
say for #$found;
} else {
say 'Not found';
}
exit 0;
sub lookup {
my $dir = shift;
my $fname = shift;
my $search = shift;
my $files;
my #items = glob("$dir/*");
for my $item (#items) {
if( -f $item && $item =~ /\b$fname\b/ ) {
my $found;
open my $fh, '<', $item or die $!;
while( my $line = <$fh> ) {
$found = 1 if $line =~ /\b$search\b/;
if( $found ) {
push #{$files}, $item;
last;
}
}
close $fh;
}
if( -d $item ) {
my $ret = lookup($item,$fname,$search);
push #{$files}, $_ for #$ret;
}
}
return $files;
}
Run as script.pl search_word
Output sample
./capacitor/data.list
./examples/data.list
./examples/test/data.list
Reference:
glob,
Perl file test operators

What is wrong in my script, tell me please

I need that scipt enter in my router on page and find the code and compared with earlier this IP information recorded, and if not changed, then stop the script.
I run this script - $ perl ~/test.pl
no error, but file my_ip.txt is not created.
In originals script must check my ip through host example.dyndns.org, but my ip is gray.
So I need to be determined through a router
#!/usr/bin/perl
use LWP::UserAgent;
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
if ($routeraddress =~ /var info_get_wanip="((\d+\.){3}(\d+))"/) {
my $ip = "$1.$2.$3.$4";
#Добавлено:
open (FILE,"my_ip.txt");
my #lines = <FILE>;
$old_ip = $lines[0]; #Считываем IP из файла
$old_ip =~ s/^\s+|\s+$//g; #trim
close(FILE);
if ($old_ip eq $ip) {
die "IP not changed"; # Выходим из скрипта, если IP не изменился
}
open (FILE,">my_ip.txt");
print FILE $ip; # Записываем в файл новый IP
close(FILE);
...... (this code is OK)
}
Do not bring the end of the code because the problem by connecting to the router
$ perl -cw -e 'print `addr admin:foo#192.168.1.1/bar`'
Possible unintended interpolation of #192 in string at -e line 1.
-e syntax OK
It doesn't work because #192 is interpolated as an array, and you are running the command addr admin:Tavolzhansky.168.1.1/RST_conn_status.htm instead of the command you meant to.
This would have been very easy to spot and fix if you would use warnings, if you would step through the code with the debugger, or as Andy suggests, if you would examine return values.
To get #192 to not be interpolated, escape the #:
my $routeraddress = `addr admin:Tavolzhansky\#192.168.1.1/RST_conn_status.htm`;
It's not working because of this:
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
if ($routeraddress =~ /var info_get_wanip="((\d+\.){3}(\d+))"/) {
...
}
Try adding a couple debug prints to confirm like:
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
print "Router IP = $routeraddress/n";
if ($routeraddress =~ /var info_get_wanip="((\d+\.){3}(\d+))"/) {
...
} else {
print "Router address didn't match.\n";
}
You should use the strict and warning pragmas, but as you said this worked when you were getting your IP from a different source, the rest of the code will work.
I'm guessing you're reading an HTML page and expecting to see this string somewhere:
var info_get_wanip="xxx.xxx.xxx.xxx";
However, you need to confirm if $routeraddress is actually getting that, it looks like
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
Is just essentially making a get request for the page, but isn't returning "var info_get_wanip="xxx.xxx.xxx.xxx";". If you sort the assignment of the variable $routeraddress, then it'll work.
I can't say how, as I don't know what it's returning now.

Perl - How to split input into an array?

I have a small Perl script that is used to take user command line input, and then splitting the input into an array where each service should be its own element.
My goal is to enable single arguments, as well as several arguments using a split character where every argument is separated by a comma sign. Examples of these would be:
ssh
ssh,named,cups
My code looks as follows, it does compile without errors but the output is not the intended.
print "Please provide the service name that you wish to analyze (Named service):\n";
print "Several services may be provided, using a comma sign as a separator.\n";
my $service_name_input = <>;
chomp($service_name_input);
my #service_list = split(/,/, $service_name_input);
foreach my $line (#service_list)
{
open(my $service_input, "service #service_list status|");
}
foreach my $line (#service_list)
{
#Matches for "running".
if ($line =~ m/(\(running\))/)
{
print "The service #service_list is running\n";
}
#Matches for "dead".
elsif ($line =~ m/(dead)/)
{
print "The service #service_list is dead\n";
}
}
The program should output if the service is running or dead, but I only get the following error code. When manually issuing the service command, it works just fine though.
The service command supports only basic LSB actions (start, stop,
restart, try-restart, reload, force-reload, status). For other
actions, please try to use systemctl.
Any help regarding the steps I should take in order to achieve a working program will be much appreciated. Thank you for reading.
foreach my $line (#service_list)
{
open(my $service_input, "service #service_list status|");
}
This loop doesn't use $line. You're passing the whole array #service_list to the service command, i.e. you're running service ssh named cups status. This causes an error because service thinks you want to execute the named action on the ssh service. To fix that, write "service $line status |".
But there's another issue: You never do anything with $service_input either. If you want to loop over each line of each service command output, you need something more like:
foreach my $service (#service_list)
{
open(my $service_input, '-|', 'service', $service, 'status')
or die "$0: can't run 'service': $!\n";
while (my $line = readline $service_input)
{
#Matches for "running".
if ($line =~ m/(\(running\))/)
{
print "The service $service is running\n";
}
#Matches for "dead".
elsif ($line =~ m/(dead)/)
{
print "The service $service is dead\n";
}
}
}
I've changed your open line to use a separate mode argument -| (which is good style in general) and the list form of process open, which avoids the shell, which is also good in general (no issues with "special characters" in $service, for example).

Search filesystem via perl script while ignoring remote mounts

I've written a perl script that is designed to search a server for world writable files. After some testing, though, I've found that I made a mistake in the logic. Specifically, I've told it to not search /. My initial thought behind this was that I was looking for locally mounted volumes while avoiding those of a remote variety (CIFS, NFS, what-have-you).
What I failed to take into consideration is that not every directory has a unique volume. As a result, by excluding / in my scan, I've missed several directories that should be included. Now I need to rework the script to include those while still excluding remote volumes.
#!/usr/bin/perl
# Directives which establish our execution environment
use warnings;
use strict;
use Fcntl ':mode';
use File::Find;
no warnings 'File::Find';
no warnings 'uninitialized';
# Variables used throughout the script
my $DIR = "/var/log/tivoli/";
my $MTAB = "/etc/mtab";
my $PERMFILE = "world_writable_w_files.txt";
my $TMPFILE = "world_writable_files.tmp";
my $EXCLUDE = "/usr/local/etc/world_writable_excludes.txt";
# Compile a list of mountpoints that need to be scanned
my #mounts;
# Create the filehandle for the /etc/mtab file
open MT, "<${MTAB}" or die "Cannot open ${MTAB}, $!";
# We only want the local mountpoints that are not "/"
while (<MT>) {
if ($_ =~ /ext[34]/) {
my #line = split;
push(#mounts, $line[1]) unless ($_ =~ /root/);
}
}
close MT;
# Read in the list of excluded files
my $regex = do {
open EXCLD, "<${EXCLUDE}" or die "Cannot open ${EXCLUDE}, $!\n";
my #ignore = <EXCLD>;
chomp #ignore;
local $" = '|';
qr/#ignore/;
};
# Create the output file path if it doesn't already exist.
mkdir "${DIR}" or die "Cannot execute mkdir on ${DIR}, $!" unless (-d "${DIR}");
# Create the filehandle for writing the findings
open WWFILE, ">${DIR}${TMPFILE}" or die "Cannot open ${DIR}${TMPFILE}, $!";
foreach (#mounts) {
# The anonymous subroutine which is executed by File::Find
find sub {
return unless -f; # Is it a regular file...
# ...and world writable.
return unless (((stat)[2] & S_IWUSR) && ((stat)[2] & S_IWGRP) && ((stat)[2] & S_IWOTH));
# Add the file to the list of found world writable files unless it is
# in the list if exclusions
print WWFILE "$File::Find::name\n" unless ($File::Find::name =~ $regex);
}, $_;
}
close WWFILE;
# If no world-writable files have been found ${TMPFILE} should be zero-size;
# Delete it so Tivoli won't alert
if (-z "${DIR}${TMPFILE}") {
unlink "${DIR}${TMPFILE}";
} else {
rename("${DIR}${TMPFILE}","${DIR}${PERMFILE}") or die "Cannot rename file ${DIR}${TMPFILE}, $!";
}
I'm at a bit of a loss as to how to approach this now. I know I can obtain the necessary information using stat -f -c %T but I don't see a similar option for perl's built-in stat (unless I'm misinterpreting the descriptions for output fields; perhaps it is found in one of the S_ variables?).
I'm just looking for a push in the right direction. I'd really rather not drop to a shell command to obtain this information.
EDIT: I've found this answer to a similar question, but it seems to be not entirely helpful. When I test the built-in stat against a CIFS mount I get 18. Perhaps what I need is a comprehensive list of values that could be returned for remote files to compare against?
EDIT2: This is the script in its new form which meets the requirements:
#!/usr/bin/perl
# Directives which establish our execution environment
use warnings;
use strict;
use Fcntl ':mode';
use File::Find;
no warnings 'File::Find';
no warnings 'uninitialized';
# Variables used throughout the script
my $DIR = "/var/log/tivoli/";
my $MTAB = "/etc/mtab";
my $PERMFILE = "world_writable_w_files.txt";
my $TMPFILE = "world_writable_files.tmp";
my $EXCLUDE = "/usr/local/etc/world_writable_excludes.txt";
my $ROOT = "/";
my #devNum;
# Create an array of the file stats for "/"
my #rootStats = stat("${ROOT}");
# Compile a list of mountpoints that need to be scanned
my #mounts;
open MT, "<${MTAB}" or die "Cannot open ${MTAB}, $!";
# We only want the local mountpoints
while (<MT>) {
if ($_ =~ /ext[34]/) {
my #line = split;
push(#mounts, $line[1]);
}
}
close MT;
# Build an array of each mountpoint's device number for future comparison
foreach (#mounts) {
my #stats = stat($_);
push(#devNum, $stats[0]);
}
# Read in the list of excluded files and create a regex from them
my $regExcld = do {
open XCLD, "<${EXCLUDE}" or die "Cannot open ${EXCLUDE}, $!\n";
my #ignore = <XCLD>;
chomp #ignore;
local $" = '|';
qr/#ignore/;
};
# Create a regex to compare file device numbers to.
my $devRegex = do {
chomp #devNum;
local $" = '|';
qr/#devNum/;
};
# Create the output file path if it doesn't already exist.
mkdir("${DIR}" or die "Cannot execute mkdir on ${DIR}, $!") unless (-d "${DIR}");
# Create our filehandle for writing our findings
open WWFILE, ">${DIR}${TMPFILE}" or die "Cannot open ${DIR}${TMPFILE}, $!";
foreach (#mounts) {
# The anonymous subroutine which is executed by File::Find
find sub {
# Is it in a basic directory, ...
return if $File::Find::dir =~ /sys|proc|dev/;
# ...a regular file, ...
return unless -f;
# ...local, ...
my #dirStats = stat($File::Find::name);
return unless $dirStats[0] =~ $devRegex;
# ...and world writable?
return unless (((stat)[2] & S_IWUSR) && ((stat)[2] & S_IWGRP) && ((stat)[2] & S_IWOTH));
# If so, add the file to the list of world writable files unless it is
# in the list if exclusions
print(WWFILE "$File::Find::name\n") unless ($File::Find::name =~ $regExcld);
}, $_;
}
close WWFILE;
# If no world-writable files have been found ${TMPFILE} should be zero-size;
# Delete it so Tivoli won't alert
if (-z "${DIR}${TMPFILE}") {
unlink "${DIR}${TMPFILE}";
} else {
rename("${DIR}${TMPFILE}","${DIR}${PERMFILE}") or die "Cannot rename file ${DIR}${TMPFILE}, $!";
}
The dev field result from stat() tells you the device number the inode lives on. That can be used to distinguish different mount points, as they'll have a different device number from the one you started at.

Issues with reducing duplicate output from log file search

This website has been a great help since I'm getting back into programming and I'm attempting to write a simple perl script that will analyze apache log files from a directory (multiple domains), pull the last 1000 lines of each log file, strip the IP addresses from the log file and then compare them with a known block list of bot spammers.
Now so far I've got the script working except for one issue. Lets say I have the IP address 10.128.45.5 in two log files, the script of course analyzes each log file in turn stripping and reducing the IP's to one PER log file but what I'm trying to do is narrow that down even more to one per instance I run this script, regardless if the same IP appears across multiple log files.
Here's the code I've gotten so far, sorry if it's a bit messy.
#!/usr/bin/perl
# Extract IP's from apache access logs for the last hour and matches with forum spam bot list.
# The fun work of Daniel Pearson
use strict;
use warnings;
use Socket;
# Declarations
my ($file,$list,#files,%ips,$match,$path,$sort);
my $timestamp = localtime(time);
# Check to see if matching file exists
$list ='list';
if (-e $list) {
Delete the file so we can download a new one if it exists
print "File Exists!";
print "Deleting File $list\n";
unlink($list);
}
sleep(5);
system ("wget http://www.domain.com/list");
sleep(5);
my $dir = $ARGV[0] or die "Need to specify the log file directory\n";
opendir(DIR, "$dir");
#files = grep(/\.*$/,readdir(DIR));
closedir(DIR);
foreach my $file(#files) {
my $sum = 0;
if (-d $file) {
print "Skipping Directory $file\n";
}
else {
$path = "$dir$file";
open my $path, "-|", "/usr/bin/tail", "-1000", "$path" or die "could not start tail on $path: $!";
my %ips;
while (my $line = <$path>) {
chomp $line;
if ($line =~ m/(?!0+\.0+\.0+\.0+$)(([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5]))/g) {
my $ip = $1;
$ips{$ip} = $ip;
}
}
}
foreach my $key (sort keys %ips) {
open ("files","$list");
while (my $sort = <files>) {
chomp $sort;
if ($key =~ $sort) {
open my $fh, '>>', 'banned.out';
print "Match Found we need to block it $key\n";
print $fh "$key:$timestamp\n";
close $fh;
}
}
}
}
Any advice that could be given I would be grateful for.
To achieve the task:
Move my %ips outside of (above) the foreach my $file (#files) loop.
Move foreach my $key ( sort keys %ips ) outside of (below) the foreach my $file (#files) loop.

Resources