Is it possible to do something like this in bash with cut:
strLf="JJT9879YGTT"
strZ=(2, 3, 5, 6, 9, 11)
numZ=${#strZ[#]}
for ((ctZ=0; ctZ<${numZ}; ctZ++))
do
lenThis=${strZ[${ctZ}]}
fetch=$(echo "${strLf}" | cut -c 1-${lenThis})
done
Through successive loops, I want ${fetch} to contain "JJ" "JJT" "JJT98" "JJT987" "JJT9879YG" "JJT9879YGTT", etc, according to the indexes given by strZ.
Or is there some other way I need to be doing this?
You can use ${string:position:length} to get the length characters of $string starting in position.
$ s="JJT9879YGTT"
$ echo ${s:0:2}
JJ
$ echo ${s:0:3}
JJT
And also using variables:
$ t=5
$ echo ${s:0:$t}
JJT98
So if you put all these values in an array, you can loop through them and use its value as a length argument saying ${string:0:length}:
strLf="JJT9879YGTT"
strZ=(2 3 5 6 9 11)
for i in ${strZ[#]}; do
echo "${strLf:0:$i}"
done
For your given string it returns this to me:
$ for i in ${strZ[#]}; do echo "${strLf:0:$i}"; done
JJ
JJT
JJT98
JJT987
JJT9879YG
JJT9879YGTT
words="JJT9879YGTT"
strZ=(2 3 5 6 9 11)
for i in "${strZ[#]}"
do
echo ${words:0:$i}
done
Output:
JJ
JJT
JJT98
JJT987
JJT9879YG
JJT9879YGTT
DEMO
Realised my mistake - I was using commas in the array to identify elements.
So instead of what I was using:
strZ=(2, 3, 5, 6, 9, 11)
I have to use:
strZ=(2 3 5 6 9 11)
It works for cut as well.
Related
Write a short program that prints each number from 1 to 100 on a new line.
• For each multiple of 3, print "Fizz" instead of the number.
• For each multiple of 5, print "Buzz" instead of the number.
• For numbers which are multiples of both 3 and 5, print "FizzBuzz" instead of the
number.
This what i have so far:
#!/bin/bash
*for i in {0..20..5}
do
echo "Number: $i"
done*
but i get this:
Number: {0..20..5}
Need help
If it's on a mac, use $(seq 0 5 20) to get the same result.
Using GNU sed
$ for ((i=1;i<=100;i++)); do echo "$i"; done | sed -E '0~3s/[0-9]+/Fizz/;0~5s/[0-9]+|$/Buzz/'
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
I'm a bit stuck with something. I have a for loop like this:
#!/bin/bash
for i in {10..15}
do
I want to obtain the last digit of the number, so if i is 12, I want to get 2. I'm having difficulties with the syntax though. I've read that I should convert it into a character array, but when I do something like:
j=${i[#]}
echo $j
I don't get 1 0 1 1 1 2 and so on...I get 10, 11, 12...How do I get the numbers to be split up so I can get the last one of i, when I don't always know how many digits will make up i (ex. it may be 1, or 10, or a 100, etc.)?
Trick is to treat $i like a string.
for i in {10..15}; do j="${i: -1}"; echo $j; done
Of course, you do not need to assign to a variable if you don't want to:
for i in {10..15}; do echo "${i: -1}"; done
This answer which uses GNU shell parameter expansion is the most sensible method, I guess.
However, you can also use the double parenthesis construct which allows C-style manipulation of variables in Bash.
for i in {10..15}
do
(( j = i % 10 )) # modulo 10 always gives the ones' digit
echo $j
done
This awk command could solve your problem:
awk '{print substr($0,length,1)}' test_file
I'm assuming that the numbers are saved in a file test_file
If you want to use for loop:
for i in `cat test_1`
do
echo $i |tail -c 2
done
I have a string
indent code by 4 spaces and 6 letters and by 5 constants
I want to extract each number separately.
1st number : 4
2nd number : 6
The string may change, so I need a one-line command that does it.
Read all numbers into an array:
readarray -t arr < \
<(grep -Eo '[[:digit:]]+' \
<<< 'indent code by 4 spaces and 6 letters and by 5 constants')
Now you can access all matches with their index:
$ echo "${arr[0]}, ${arr[1]}"
4, 6
I would like to order a list of files by their size, but comparing it with a specific number (another file size) being the rule to compare the absolute distance.
This has to be done with a bash script.
For instance:
Size to compare: 5
List of files sizes: { 1, 2, 6, 10, 5 }
Result: {5, 6, 2, 1, 10 }
I am far from being an expert in bash coding, so I would appreciate some help here.
size=5
source=(1 2 6 10 5)
for i in ${source[#]}; do j=$((i-size)); echo ${j/-/} $i; done | sort -n | cut -d " " -f 2 | tr "\n" " "
Output:
5 6 2 1 10
This solution also uses Schwartzian Transform mentioned by chepner.
Use a Schwartzian Transform:
printf "%d\n" 1 2 6 10 5 |
# Decorate
perl -ne 'printf "%d %d\n", abs($_ - 5), $_' |
# Sort
sort -k1,1n |
# Undecorate
awk '{print $2}'
I'm only using Perl because it's the shortest way I could think to access an absolute value function.
Perl can be called from a bash script, since it's installed everywhere.
perl -e '$n=shift; #A=split/,/,(shift); print join ", ", sort {abs($a-$n)<=>abs($b-$n)} #A' 5 1,2,6,10,5
output:
5, 6, 2, 1, 10
$n is set to your number 5 using shift
Array #A is set by splitting the input string using commas as a delimiter
The array is printed using a custom sort function sort {abs($a-$n)<=>abs($b-$n)}
Variation assuming your input file sizes are on separate lines:
printf "%d\n" 1 2 6 10 5 | perl -ne 'BEGIN{$n=shift} push #A, $_; END{print join "", sort {abs($a-$n)<=>abs($b-$n)} #A}' 5
output:
5
6
2
1
10
I don’t do this stuff for a living so forgive me if it’s a simple question (or more complicated than I think). I‘ve been digging through the archives and found a lot of tips that are close but being a novice I’m not sure how to tweak for my needs or they are way beyond my understanding.
I have some large data files that I can parse out to generate a list of coordinate that are mostly sequential
5
6
7
8
15
16
17
25
26
27
What I want is a list of the gaps
1-4
9-14
18-24
I don’t know perl, SQL or anything fancy but thought I might be able to do something that would subtract one number from the next. I could then at least grep the output where the difference was not 1 or -1 and work with that to get the gaps.
With awk :
awk '$1!=p+1{print p+1"-"$1-1}{p=$1}' file.txt
explanations
$1 is the first column from current input line
p is the previous value of the last line
so ($1!=p+1) is a condition : if $1 is different than previous value +1, then :
this part is executed : {print p+1 "-" $1-1} : print previous value +1, the - character and fist columns + 1
{p=$1} is executed for each lines : p is assigned to the current 1st column
interesting question.
sputnick's awk one-liner is nice. I cannot write a simpler one than his. I just add another way using diff:
seq $(tail -1 file)|diff - file|grep -Po '.*(?=d)'
the output with your example would be:
1,4
9,14
18,24
I knew that there is comma in it, instead of -. you could replace the grep with sed to get -, grep cannot change the input text... but the idea is same.
hope it helps.
A Ruby Answer
Perhaps someone else can give you the Bash or Awk solution you asked for. However, I think any shell-based answer is likely to be extremely localized for your data set, and not very extendable. Solving the problem in Ruby is fairly simple, and provides you with flexible formatting and more options for manipulating the data set in other ways down the road. YMMV.
#!/usr/bin/env ruby
# You could read from a file if you prefer,
# but this is your provided corpus.
nums = [5, 6, 7, 8, 15, 16, 17, 25, 26, 27]
# Find gaps between zero and first digit.
nums.unshift 0
# Create array of arrays containing missing digits.
missing_nums = nums.each_cons(2).map do |array|
(array.first.succ...array.last).to_a unless
array.first.succ == array.last
end.compact
# => [[1, 2, 3, 4], [9, 10, 11, 12, 13, 14], [18, 19, 20, 21, 22, 23, 24]]
# Format the results any way you want.
puts missing_nums.map { |ary| "#{ary.first}-#{ary.last}" }
Given your current corpus, this yields the following on standard output:
1-4
9-14
18-24
Just remember the previous number and verify that the current one is the previous plus one:
#! /bin/bash
previous=0
while read n ; do
if (( n != previous + 1 )) ; then
echo $(( previous + 1 ))-$(( n - 1 ))
fi
previous=$n
done
You might need to add some checking to prevent lines like 28-28 for single number gaps.
Perl solution similar to awk solution from StardustOne:
perl -ane 'if ($F[0] != $p+1) {printf "%d-%d\n",$p+1,$F[0]-1}; $p=$F[0]' file.txt
These command-line options are used:
-n loop around every line of the input file, do not automatically print every line
-a autosplit mode – split input lines into the #F array. Defaults to splitting on whitespace. Fields are indexed starting with 0.
-e execute the perl code
Given input file, use the numinterval util and paste its output beside file, then munge it with tr, xargs, sed and printf:
gaps() { paste <(echo; numinterval "$1" | tr 1 '-' | tr -d '[02-9]') "$1" |
tr -d '[:blank:]' | xargs echo |
sed 's/ -/-/g;s/-[^ ]*-/-/g' | xargs printf "%s\n" ; }
Output of gaps file:
5-8
15-17
25-27
How it works. The output of paste <(echo; numinterval file) file looks like:
5
1 6
1 7
1 8
7 15
1 16
1 17
8 25
1 26
1 27
From there we mainly replace things in column #1, and tweak the spacing. The 1s are replaced with -s, and the higher numbers are blanked. Remove some blanks with tr. Replace runs of hyphens like "5-6-7-8" with a single hyphen "5-8", and that's the output.
This one list the ones who breaks the sequence from a list.
Idea taken from #choroba but done with a for.
#! /bin/bash
previous=0
n=$( cat listaNums.txt )
for number in $n
do
numListed=$(($number - 1))
if [ $numListed != $previous ] && [ $number != 2147483647 ]; then
echo $numListed
fi
previous=$number
done