If I have an IP address say 192.168.1.2, I need to generate the next 100 IPs such that it keeps adding to the base ip.
I tried to execute echo 192.168.1.{2..102} this works fine but I need a generic code that can generate as many IP addresses specified with the base IP address using Shell Script.
Something like:
#!/bin/bash
i=1
while (( i <= 100 ))
do
echo "x.y.z.$i"
i=$((i + 1))
done
You can use a for to iterate through a list created by the command seq. Then, just modify the numbers in seq as first and last number
#!/bin/bash
START=0
END=100
for I in $(seq $START $END)
do
echo "192.168.0.$I"
done
If I need to change up to second octet then I should be able to change.
For example. 198.168.X.Y. I need to generate the ip for X and Y
If I need to change up to three octets then I should be able to change.
Am looking for a solution for this. seq -f 198.168.1.%g 1 100 works fine, need a script that can change the other octets
IP addresses are, of course, simply unsigned 32 bit numbers. However they are usually expressed as bytes, in decimal, with dots between the numbers.
So, if you need to use this "dotted quad" representation you can just convert the starting one back to an integer, count out how many you want, and for each one you count up, print it back as a dotted quad.
With any shell supporting arithmetic expressions (with bitwise operators, and for added convenience, arrays), it's easy:
#!/bin/ksh
ip="192.168.240.240"
num=100
set -A iparr $(echo $ip | sed 's/\./ /g')
nip=$((iparr[0] << 24))
nip=$((nip | iparr[1] << 16))
nip=$((nip | iparr[2] << 8))
nip=$((nip | iparr[3]))
lnip=$((nip + num))
while [ $nip -lt $lnip ]; do
let $((nip++))
iparr[0]=$((nip >> 24))
iparr[1]=$(((nip >> 16) & 255))
iparr[2]=$(((nip >> 8) & 255))
iparr[3]=$((nip & 255))
printf "%d.%d.%d.%d\n" ${iparr[0]} ${iparr[1]} ${iparr[2]} ${iparr[3]}
done
(It's not so easy with an older shell though -- and neither awk nor expr do bitwise operations on integers very easily. But then maybe the tool you're feeding these numbers to will accept plain integers, or hex numbers, so you may not really need to use dotted quad notation.)
Related
I know I can do this to reflect just last 2 directories in the PS1 value.
PS1=${PWD#"${PWD%/*/*}/"}#
but lets say we have a directory name that's really messy and will reduce my working space , like
T-Mob/2021-07-23--07-48-49_xperia-build-20191119010027#
OR
2021-07-23--07-48-49_nokia-build-20191119010027/T-Mob#
those are the last 2 directories before the prompt
I want to set a condition if directory length of either of the last 2 directories is more than a threshold e.g. 10 chars , shorten the name with 1st 3 and last 3 chars of the directory (s) whose length exceeds 10
e.g.
2021-07-23--07-48-49_xperia-build-20191119010027 &
2021-07-23--07-48-49_nokia-build-20191119010027
both gt 10 will be shortened to 202*027 & PS1 will be respectively
T-Mob/202*027/# for T-Mob/2021-07-23--07-48-49_xperia-build-20191119010027# and
202*027/T-Mob# for 2021-07-23--07-48-49_nokia-build-20191119010027/T-Mob#
A quick 1 Liner to get this done ?
I cant post this in comments so Updating here. Ref to Joaquins Answer ( thx J)
PS1=''`echo ${PWD#"${PWD%/*/*}/"} | awk -v RS='/' 'length() <=10{printf $0"/"}; length()>10{printf "%s*%s/", substr($0,1,3), substr($0,length()-2,3)};'| tr -d "\n"; echo "#"`''
see below o/p's
/root/my-applications/bin # it shortened as expected
my-*ons/bin/#cd - # going back to prev.
/root
my-*ons/bin/# #value of prompt is the same but I am in /root
A one-liner is basically always the wrong choice. Write code to be robust, readable and maintainable (and, for something that's called frequently or in a tight loop, to be efficient) -- not to be terse.
Assuming availability of bash 4.3 or newer:
# Given a string, a separator, and a max length, shorten any segments that are
# longer than the max length.
shortenLongSegments() {
local -n destVar=$1; shift # arg1: where do we write our result?
local maxLength=$1; shift # arg2: what's the maximum length?
local IFS=$1; shift # arg3: what character do we split into segments on?
read -r -a allSegments <<<"$1"; shift # arg4: break into an array
for segmentIdx in "${!allSegments[#]}"; do # iterate over array indices
segment=${allSegments[$segmentIdx]} # look up value for index
if (( ${#segment} > maxLength )); then # value over maxLength chars?
segment="${segment:0:3}*${segment:${#segment}-3:3}" # build a short version
allSegments[$segmentIdx]=$segment # store shortened version in array
fi
done
printf -v destVar '%s\n' "${allSegments[*]}" # build result string from array
}
# function to call from PROMPT_COMMAND to actually build a new PS1
buildNewPs1() {
# declare our locals to avoid polluting global namespace
local shorterPath
# but to cache where we last ran, we need a global; be explicit.
declare -g buildNewPs1_lastDir
# do nothing if the directory hasn't changed
[[ $PWD = "$buildNewPs1_lastDir" ]] && return 0
shortenLongSegments shorterPath 10 / "$PWD"
PS1="${shorterPath}\$"
# update the global tracking where we last ran this code
buildNewPs1_lastDir=$PWD
}
PROMPT_COMMAND=buildNewPs1 # call buildNewPs1 before rendering the prompt
Note that printf -v destVar %s "valueToStore" is used to write to variables in-place, to avoid the performance overhead of var=$(someFunction). Similarly, we're using the bash 4.3 feature namevars -- accessible with local -n or declare -n -- to allow destination variable names to be parameterized without the security risk of eval.
If you really want to make this logic only apply to the last two directory names (though I don't see why that would be better than applying it to all of them), you can do that easily enough:
buildNewPs1() {
local pathPrefix pathFinalSegments
pathPrefix=${PWD%/*/*} # everything but the last 2 segments
pathSuffix=${PWD#"$pathPrefix"} # only the last 2 segments
# shorten the last 2 segments, store in a separate variable
shortenLongSegments pathSuffixShortened 10 / "$pathSuffix"
# combine the unshortened prefix with the shortened suffix
PS1="${pathPrefix}${pathSuffixShortened}\$"
}
...adding the performance optimization that only rebuilds PS1 when the directory changed to this version is left as an exercise to the reader.
Probably not the best solution, but a quick solution using awk:
PS1=`echo ${PWD#"${PWD%/*/*}/"} | awk -v RS='/' 'length()<=10{printf $0"/"}; length()>10{printf "%s*%s/", substr($0,1,3), substr($0,length()-2,3)};'| tr -d "\n"; echo "#"`
I got this results with your examples:
T-Mob/202*027/#
202*027/T-Mob/#
This question already has answers here:
Iterate over two arrays simultaneously in bash
(6 answers)
Closed 1 year ago.
I have 10 text files and I want to paste each file with its pair, such that I have 5 total files.
I tried the following:
for i in 4_1 5_1 6_1 7_1 8_1
do
for j in 4_2 5_2 6_2 7_2 8_2
do
paste ${i}.txt ${j}.txt > ${i}.${j}.txt
done
done
However, this code combines every possible combination instead of just combining the matching pairs.
So I would like file 4_1.txt to be paired with 4_2.txt, 5_1.txt with 5_2.txt, etc.
I agree with the answer currently proposed by fedorqui in the context of the question currently asked. The below is given only to provide some more general answers.
One more general approach (for bash 4.0 or newer) is to store your pairs in an associative array:
declare -A pairs=( [4_1]=4_2 [5_1]=5_2 [6_1]=6_2 [7_1]=7_2 [8_1]=8_2 )
for i in "${!pairs[#]}"; do
j=${pairs[$i]}
paste "$i.txt" "$j.txt" >"${i}.${j}.txt"
done
Another (compatible with older releases of bash) is to use more than one conventional array:
is=( 4_1 5_1 6_1 7_1 8_1 )
js=( 4_2 5_2 6_2 7_2 8_2 )
for idx in "${!is[#]}"; do
i=${is[$idx]}
j=${js[$idx]}
paste "$i.txt" "$j.txt" >"$i.$j.txt"
done
Simplest so far:
for i in "1 a" "2 b" "3 c"; do a=( $i ); echo "${a[1]}"; echo "${a[0]}"; done
a
1
b
2
c
3
You can use an associative array:
animals=(dog cat mouse)
declare -A size=(
[dog]=big
[cat]=medium
[mouse]=small
)
declare -A sound=(
[dog]=barks
[cat]=purrs
[mouse]=cheeps
)
for animal in "${animals[#]}"; do
echo "$animal is ${size[$animal]} and it ${sound[$animal]}"
done
This allows you traversing pairs, triples, etc. Credits: the original idea is taken from #CharlesDuffy-s answer.
If you want to use one variable and perform and action with it, you just need to use one loop:
for file in 4 5 6 7 8
do
paste "${file}_1" "${file}_2"
done
This will do
paste 4_1 4_2
paste 5_1 5_2
...
the above did not work for me, but the following does read values in pairs from an ordered list
(can be more than pairs adding extra 'read-lines' :-)
while read x; do
read y
echo "$x $y"
done << '___HERE'
X1
Y1
X2
Y2
X3
Y3
___HERE
produces
X1 Y1
X2 Y2
X3 Y3
There is a common pattern where you have pairs of files, where one name of the pair can be easily derived from the other. If the file you know the name of is X and the other file is Y, you have the following common use cases.
For renaming, Y is X with an extension removed and/or a date stamp added.
For transcoding, Y is X with a different extension and perhaps a different directory.
For many data analysis tasks, X and Y share some parts of the file name, but have different parameters or extensions.
All of these lend themselves to the same rough code skeleton.
for x in path/to/base*.ext; do
dir=${x%/*} # Trim trailing file name, keep dir
base=${x##*/} # Trim any leading directory
# In this case, $y has a different subdirectory and a different extension
y=${dir%/to}/from/${base%.ext}.newext
# Maybe check if y exists? Or doesn't exist?
if [ -e "$y" ]; then
echo "$0: $y already exists -- skipping" >&2
continue
fi
mv or ffmpeg or awk or whatever "$x" and "$y"
done
The key here is the observation that y can be derived from x with some simple variable substitutions. So you loop over the x values, and figure out the corresponding y value inside the loop.
Here, we have used the shell's built-in ${variable#prefix} and ${variable%suffix} operators to return the variable's value with any leading prefix or trailing suffix, respectively, trimmed off. (There is also ## and %% to match the longest, instead of the shortest, possible match. The expression after # or % is a regular shell glob pattern.) These should usually be all you need, although you frequently see sed or awk scripts even for this trivial job (where really you should usually try to avoid an external process), as well as of course for more demanding transformations.
If you need to loop over x files scattered across different directories, maybe the loop should start with something like
find dir1 dir2 etc/and/so/forth -type f -name 'x-files*.ext' -print |
while IFS='' read -r x; do
:
A commonly seen problem in similar questions is answers which fail to quote $x and $y correctly. Generally, any variable containing a file name should always be in double quotes.
Where X and Y are unrelated, a common solution is to loop over a here document containing the mapping:
while read -r x y; do
: stuff with "$x" and "$y"
done <<'____HERE'
first_x_value first_y_value
another_x corresponding_y
random surprise
____HERE
I have a string of the form: "8, 14-24, 30-45, 9", which is a substring of the output of pbsnodes. This shows the cores in use on a given node, where 14-24 is a range of cores in use.
I'd like to know the total number of cores in use from this string, i.e.
1 + (24 - 14 + 1) + (45 - 30 + 1 )+ 1 in this example, using a bash script.
Any suggestions or help is much appreciated.
Michael
You could use pure bash techniques to achieve this. By reading the string to array and doing the arithmetic operator using the $((..)) operator. You can run these commands directly on the command-line,
IFS=", " read -ra numArray <<<"8, 14-24, 30-45, 9"
unset count
for word in "${numArray[#]}"; do
(( ${#word} == 1 )) && ((++count)) || count=$(( count + ${word#*-} - ${word%-*} + 1 ))
done
printf "%s\n" "$count"
The idea is
The read with -a splits the string on the de-limiter set by IFS and reads it into the array numArray
A loop on each of the elements, for each element, if the element is just single character, just increment total count by 1
For numeric ranges, do manipulation as e.g. for number a-b use parameter expansion syntax ${word#*-} and ${word%-*} to extract b and a respectively and do b-a+1 and add it with count calculated already and print the element after the loop
You can put this in a bash script with she-bang set to #!/bin/bash and run the script or run it directly from the command-line
In bash I'm trying to create an array and then run through a loop a number of times (determined by the user of the file) and then add a choice to that array the predetermined number of times. This is trade data, so for example, I choose 2 for factors. Then the program asks me to input the factor I want, and I put in open (open price of the day), then bid is added to the array arr and the question is asked again. Then I put in close (close price of the day) then close is added to the array, and in the end the
arr = open close like that. But I run the code and the question: "How many factors would you like to check total: " simply runs over and over again and I never leave the loop and it never appears that the inputs are being put into the array. Any help as to my mistake here is greatly appreciated. Thanks.
factor=""
total=0
declare -a arr
read -p "How many factors would you like to check total: " -e -i "$total" total
for (( x=1; x=total; x++ ))
do
read -p "Enter factor from list: " -e -i "$factor" factor
arr+=(${arr[#]} "$factor")
done
echo ${arr[#]}
You almost got it correct on array append. Just remember that += operator doesn't need full reference to array again on RHS. e.g. just
arr+=($factor)
Would be suffice to append $factor at the end of array variable arr.
Modify your script a little bit like this:
factor=""
total=0
declare -a arr
read -p "How many factors would you like to check total: " -e -i "$total" total
for (( x=1; x<=total; x++ ))
do
read -p "Enter factor from list: " -e -i "$factor" factor
arr+=($factor)
done
echo ${arr[#]}
You have a typo
for (( x=1; x=total; x++ ))
should be
for (( x=1; x==total; x++ ))
In the first one you are assigning total to x which is always true. In the second one you are checking for equality.
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.