cat * mixes up the order of files - bash

Right now, I am working on a "Text Editor" made with Bash. Everything was going perfectly until I tested it. When I opened the file the script created, everything was jumbled up. I eventually figured out it had something to do with the cat BASHTE/* >> $file I had put in. I still have no idea why this happens. My crappy original code is below:
#!/bin/bash
# ripoff vim
clear
echo "###############################################################################"
echo "# BASHTE TEXT EDITOR - \\\ = interupt :q = quit :w = write #"
echo "# :wq = Write and quit :q! = quit and discard :dd = Delete Previous line #"
echo "###############################################################################"
echo ""
read -p "Enter file name: " file
touch .$file
mkdir BASHTE
clear
echo "###############################################################################"
echo "# BASHTE TEXT EDITOR - \\\ = interupt :q = quit :w = write #"
echo "# :wq = Write and quit :q! = quit and discard :dd = Delete Previous line #"
echo "###############################################################################"
while true
do
read -p "$lines >" store
if [ "$store" = "\\:q" ]
then
break
elif [ "$store" = "\\:w" ]
then
cat BASHTE/* >> $file
elif [ "$store" = "\\:wq" ]
then
cat BASHTE/* >> $file
rm -rf .$file
break
elif [ "$store" = "\\:q!" ]
then
rm -rf BASHTE
rm -rf $file
break
elif [ "$store" = "\\:dd" ]
then
LinesMinusOne=$(expr $lines - 1)
rm -rf BASHTE/$LinesMinusOne.txt
else
echo $store >> BASHTE/$lines.txt
# counts the number of times the while loop is run
((lines++))
fi
done
This is what I got after I typed in the alphabet:
b
j
k
l
m
n
o
p
q
r
s
c
t
u
v
w
x
y
z
d
e
f
g
h
I
This was what I inputted
a
v
c
d
e
f
g
h
I
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
\\:wq
Any help would be great, Thanks

BASHTE/* is in lexical order, so every line starting with 1 will come before every line starting with 2 and so on. That means the order of your input lines is:
1 a
10 j
11 k
12 l
...and so on...
To make the lines sort well with the * operator, you'll need to name them with leading zeros, for example:
# ...
echo $store >> BASHTE/$(printf %020d $lines).txt
# ...
I chose the %020d format because it should store any number of lines applicable for a 64-bit system, since 2 ** 64 = 18446744073709551616, which is 20 digits long.

Related

Reading in input from text file, Bash script

I am new to writing scripts in Bash and I am working on an assignment to make an inventory system. I have written all the scripts and they all work from using the standard input terminal. I now am looking to take the inputs from a text file called a1Input.txt which has all inputs on a new line.
r
9823
r
5430
c
7777
sml_widget
Small Widget (1 oz.)
6.99
15
50
Small, white, widget w/o packaging
r
7777
d
9823
r
9823
r
3293
u
3293
29.99
33
75
r
3293
The code for my initial bash script is this
#!/bin/bash
# assign1.bash
shopt -s nocasematch
option=""
until [ "$option" = "F" ]; do
echo "C - create a new item"
echo "R - read an existing item"
echo "U - update an existing item"
echo "D - delete an existing item"
echo "T - total price of an item"
echo "Choose a option"
read option
case $option in
C)
./create.bash
;;R)
./read.bash
;;U)
./update.bash
;;D)
./delete.bash
;;T)
./total.bash
;;*)
echo "Invalid input"
esac
done
What i would do to inject inputs from file to an interactive script is just:
cat a1Input.txt | ./interactive_script.sh
For example, imagine this simple script (copy-paste on terminal to create):
cat << EOF > questions.sh
#!/bin/bash
echo "What's your name ?"
read name
echo "What is your favourite movie ?"
read movie
echo "Hi \$name, i also love '\$movie' movie !!"
EOF
And these inputs:
cat << EOF > inputs.txt
Edu
Interstellar
EOF
Then, just execute:
chmod a+x questions.sh
cat inputs.txt | ./questions.sh
If your script is more complicated, consider using "expect", although this is quite complex.
BRs

extracting vdate form nc header with bash without opening file

I have to change the title of a couple of hundred files by adding the vdate from its header to its title.
If vdate = 19971222, then I want the name of that nc file to become rerun4_spindown_19971222.nc
I know I can find the vdate by ncdump -h filename (see example header below).
ncdump -h rerun4_1997_spindown_09191414_co2
netcdf rerun4_1997_spindown_09191414_co2 {
dimensions:
lon = 768 ;
lat = 384 ;
nhgl = 192 ;
nlevp1 = 96 ;
spc = 32896 ;
// global attributes:
:file_type = "Restart history file" ;
:source_type = "IEEE" ;
:history = "" ;
:user = " Linda" ;
:created = " Date - 20190919 Time - 134447" ;
:label_1 = " Atmospheric model " ;
:label_2 = " Library 23-Feb-2012" ;
:label_3 = " Lin & Rood ADVECTION is default" ;
:label_4 = " Modified physics" ;
:label_5 = " Modified radiation" ;
:label_6 = " Date - 20190919 Time - 134447" ;
:label_7 = " Linda " ;
:label_8 = " Linux " ;
:fdate = 19950110 ;
:ftime = 0 ;
:vdate = 19971222 ;
:vtime = 235800 ;
:nstep = 776158 ;
:timestep = 120. ;
However, then I have to manually open all the files and manually change the title of the file... of hundreds of files. I would prefer making a bash that can automatically do that.
I am sure there must be a more intelligent way to extract the vdate from the nc header, could you guys help me out?
Thank you!
In theory, something like that should work:
#! /bin/sh
for file in rerun4_*_spindown_* ; do
vdate=$(ncdump -h $file | awk '$1 == ":vdate" { print $3 }')
new_name="rerun4_spindown_$vdate.nc"
mv "$file" "$new_name"
done
I do not have access to netCDF files - more testing is needed.

Menu based shell script to delete files

My requirement is to delete file based on it displayed. Following is code snippet where I listed files but when I select option it displays file and when I capture file name its not happening only getting key not VALUE($REPLY only displays key but not value). Can someone help me out.
#!/bin/bash
select list in $(ls *.tmp)
do
echo $list
echo Do you want to delete files ?
read userInput
echo "UserInput is :: " $userInput
echo "Reply is :: " $REPLY
if [ $userInput == $REPLY ] ; then
# rm $REPLY
echo 'Yes'
break
done
----OUTPUT-----
1) +~JF1905393034413613060.tmp
2) +~JF2032005334435574091.tmp
3) +~JF3116454937363220082.tmp
4) +~JF3334986634800781310.tmp
5) +~JF3651229840772890748.tmp
6) +~JF3882306323060007639.tmp
7) +~JF573641658479505435.tmp
8) +~JF6137053351660236007.tmp
9) +~JF6277682393160684532.tmp
10) +~JF6385610668752278364.tmp
11) +~JF6824954027739238354.tmp
12) +~JF7876557427734797684.tmp
#? 4
+~JF3334986634800781310.tmp
Do you want to delete files ?
y
UserInput is :: y
Reply is :: 4
No
Try this:
PS3="Pick a file number to delete (or Ctrl-C to quit): "
select f in *.tmp ; do echo rm "$f" ; done
Then remove the echo to make it actually delete files.

Split very large files on record border

I have to split a very large file into N smaller files with the following constraints:
I have to split on record border
Record separator can be any character
the number of records in the resulting N files should be the same (+/- 1 record)
I can just use bash and standard coreutils (I have a working solution in Perl but we're not allowed to install Perl/Python/etc)
This is not a real constraint but - if possible - I'd like to scan the original (large) file just once.
Sort order of the resulting files is not important.
My working solution in Perl reads the original file and writes...
- the 1st record to the first file
- ...
- the Nth record to the Nth file
- the N+1 record back to the first file
- etc
So - at the end - with a single scan of the initial file I do get several smaller files with the same number of records (+/- 1).
For example, assume this is the input file:
1,1,1,1A2,2,2,2A3,
3,3,3A4,4,4,4A5,5,
5,5A6,6,6,6A7,7,7,
7,A8,8,8,8A9,9,9,9
A0,0,0,0
With record separator = 'A' and N = 3 I should get three files:
# First file:
1,1,1,1A2,2,2,2A3,
3,3,3
# Second file
4,4,4,4A5,5,
5,5A6,6,6,6
# Third file:
7,7,7,
7,A8,8,8,8A9,9,9,9
A0,0,0,0
UPDATE
Here you have the perl code. I tried to make it as simple and readable as I can:
#!/usr/bin/perl
use warnings;
use strict;
use locale;
use Getopt::Std;
#-----------------------------------------------------------------------------
# Declaring variables
#-----------------------------------------------------------------------------
my %op = (); # Command line parameters hash
my $line = 0; # Output file line number
my $fnum = 0; # Output file number
my #fout = (); # Output file names array
my #fhnd = (); # Output file handles array
my #ifiles = (); # Input file names
my $i = 0; # Loop variable
#-----------------------------------------------------------------------------
# Handling command line arguments
#-----------------------------------------------------------------------------
getopts("o:n:hvr:", \%op);
die "Usage: lfsplit [-h] -n number_of_files",
" [-o outfile_prefix] [-r rec_sep_decimal] [-v] input_file(s)\n"
if $op{h} ;
if ( #ARGV ) {
#ifiles = #ARGV ;
} else {
die "No input files...\n" ;
}
$/ = chr($op{r}) if $op{r} ;
#-----------------------------------------------------------------------------
# Setting Default values
#-----------------------------------------------------------------------------
$op{o} |= 'out_' ;
#-----------------------------------------------------------------------------
# Body - split in round-robin to $op{n} files
#-----------------------------------------------------------------------------
for ( $i = 0 ; $i < $op{n} ; $i++ ) {
local *OUT ; # Localize file glob
$fout[$i] = sprintf "%s_%04d.out", $op{o}, $i ;
open ( OUT, "> $fout[$i]" ) or
die "[lfsplit] Error writing to $fout[$i]: $!\n";
push ( #fhnd , *OUT ) ;
}
$i = 0 ;
foreach ( #ifiles ) {
print "Now reading $_ ..." if $op{v} ;
open ( IN, "< $_" ) or
die "[lfsplit] Error reading $op{i}: $!\n" ;
while ( <IN> ) {
print { $fhnd[$i] } $_ ;
$i = 0 if ++$i >= $op{n} ;
}
close IN ;
}
for ( $i = 0 ; $i < $op{n} ; $i++ ) {
close $fhnd[$i] ;
}
#-----------------------------------------------------------------------------
# Exit
#-----------------------------------------------------------------------------
exit 0 ;
Just for kicks, a pure bash solution, no external programs and no forking (I think):
#!/bin/bash
input=$1
separator=$2
outputs=$3
i=0
while read -r -d"$separator" record; do
out=$((i % outputs)).txt
if ((i < outputs)); then
: > $out
else
echo -n "$separator" >> $out
fi
echo -n "$record" >> $out
((i++))
done < $input
Sadly this will reopen every file for every output operation. I'm sure it's possible to fix this, using <> to open a file descriptor and keep it open, but using that with non-literal file descriptors is a bit of a pain.

Difference between local a and local a=

from /lib/lsb/init-functions (maybe this file is debian specific, but doesn't really matter for the question):
pidofproc () {
local pidfile line i pids= status specified pid
pidfile=
specified=
Whats the difference between saying
local a
and
local a=
?
Both types remove any external versions of the variables from the scope.
The = assigns a null value to the variable, whereas the bare form leaves the variable unset.
For example:
A=30
B=30
function foo()
{
local A B=
echo A - $A
echo B - $B
echo A :- ${A:-minusA}
echo B :- ${B:-minusB}
echo A :+ ${A:+plusA}
echo B :+ ${B:+plusB}
echo A hash ${#A}
echo B hash ${#B}
echo A - ${A-minusA}
echo B - ${B-minusB}
echo A + ${A+plusA}
echo B + ${B+plusB}
## Modifies variable
echo A := ${A:=eqA}
echo B := ${B:=eqB}
echo A - $A
echo B - $B
}
foo
Output:
A -
B -
A :- minusA
B :- minusB
A :+
B :+
A hash 0
B hash 0
A - minusA
B -
A +
B + plusB
A := eqA
B := eqB
A - eqA
B - eqB
You can see the section:
echo A - ${A-minusA}
echo B - ${B-minusB}
echo A + ${A+plusA}
echo B + ${B+plusB}
is different for A and B.

Resources