I am writing a bash script named safeDel.sh with base functionalities including:
file [file1, file2, file3...]
-l
-t
-d
-m
-k
-r arg
For the single letter arguments I am using the built in function getops which works fine. The issue I'm having now is with the 'file' argument. The 'file' argument should take a list of files to be moved to a directory like this:
$ ./safeDel.sh file file1.txt file2.txt file3.txt
The following is a snippet of the start of my program :
#! /bin/bash
files=("$#")
arg="$1"
echo "arguments: $arg $files"
The echo statement shows the following:
$ arguments : file file
How can I split up the file argument from the files that have to be moved to the directory?
Assuming that the options processed by getopts have been shifted off the command line arguments list, and that a check has been done to ensure that at least two arguments remain, this code should do what is needed:
arg=$1
files=( "${#:2}" )
echo "arguments: $arg ${files[*]}"
files=( "${#:2}" ) puts all the command line arguments after the first into an array called files. See Handling positional parameters [Bash Hackers Wiki] for more information.
${files[*]} expands to the list of files in the files array inside the argument to echo. To safely expand the list in files for looping, or to pass to a command, use "${files[#]}". See Arrays [Bash Hackers Wiki].
This is a way you can achieve your needs:
#!/bin/bash
declare -a files="$#"
for fileToManage in ${files}; do
echo "Managing ... $fileToManage"
done
But it works only if there is no space in your file names, in which case you need to do some additional work.
Let me know if you need further help.
function getting_arguments {
# using windows powershell
echo #($args).GetType()
echo #($args).length
echo "#($args)[0]"
echo #($args)[0]
echo "#($args)[1..(#($args).length)]"
echo #($args)[1..(#($args).length)]
echo "debug: $(#($args)[0])" #($args)[1..(#($args).length)]
}
OUTPUT
PS C:\monkey> getting_arguments 1 2 3 4 5 6
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
6
#(1 2 3 4 5 6)[0]
1
#(1 2 3 4 5 6)[1..(#(1 2 3 4 5 6).length)]
2
3
4
5
6
debug: 1
2
3
4
5
6
Related
If I run the
echo {0..9}
command, then I get the following output:
0 1 2 3 4 5 6 7 8 9
Can I somehow put the string "0 1 2 3 4 5 6 7 8 9" into a variable inside bash script? I only found a way using echo:
x=`echo {0..9}`
But this method implies the execution of an external program. Is it possible to somehow manage only with bash?
Interested, rather than a way to convert a range to a string, but additionally concatenate with a string, for example:
datafiles=`echo data{0..9}.txt`
First of all,
x=`echo {0..9}`
doesn't call an external program (echo is a built-in) but creates a subshell. If it isn't desired you can use printf (a built-in as well) with -v option:
printf -v x ' %s' {0..9}
x=${x:1} # strip off the leading space
or
printf -v datafiles ' data%s.txt' {0..9}
datafiles=${datafiles:1}
or you may want storing them in an array:
datafiles=(data{0..9}.txt)
echo "${datafiles[#]}"
This last method will work correctly even if filenames contain whitespace characters:
datafiles=(data\ {0..9}\ .txt)
printf '%s\n' "${datafiles[#]}"
I have 5 folders in Test directory with name output001 to output005. Each output folders have files with filename *.cl_evt in it. I want to enter the each output folders and want to run the below command in bash shell script. How to iterate through each folder and run the below code through bash shell script ?
#!/bin/bash
echo -e "sw00092413002xpcw3po_cl.evt
sw00092413002xpcw3po_cl_bary4.evt
sw00092413002sao.fits.gz" | barycorr ra=253.467570 dec=39.760169
You can use the command seq (with a format) to generate the numbers in the name of your directories... Like that:
for i in $(seq -f "%03g" 1 5); do
cd output$i
echo -e "sw00092413002xpcw3po_cl.evt
sw00092413002xpcw3po_cl_bary4.evt
sw00092413002sao.fits.gz" | barycorr ra=253.467570 dec=39.760169
cd ..
done
If you want to adjust the name of the data file in each of the sub-directories, you can write it this way:
for i in $(seq -f "%03g" 1 5); do
cd output$i
f1=$(ls sw*pcw3po_cl.evt 2>/dev/null)
if [ "$f1" == "" ] ; then
echo "no data file in directory output$i"
continue
else
f2=${f1/pcw3po_cl.evt/pcw3po_cl_bary4.evt}
f3=$(ls sw*sao.fits.gz)
echo -e "$f1
$f2
$f3" | barycorr ra=253.467570 dec=39.760169
fi
cd ..
done
Break-down of the script:
the command :
seq -f "%03g" 1 5
generates the sequence:
001
002
003
004
005
Which means that the loop :
for i in $(seq -f "%03g" 1 5); do
will loop 5 times, with variable i successively taking the value 001, 002, 003, 004, 005
Inside each loop, we're running a few commands. (Let's analyse the first loop when i contains 001) :
the fist line of the loop :
cd output$i
is equivalent to output001 (so we go to the directory output001).
The last line of the loop:
cd ..
goes up one level in the directory tree (i.e. it returns to the directory where you were at the beginning of the script).
The following command executes : ls sw*pcw3po_cl.evt inside the directory output001 and puts the result in the variable f1 :
f1=$(ls sw*pcw3po_cl.evt)
The following command takes variable f1, substitutes the string pcw3po_cl.evt with pcw3po_cl_bary4.evt and puts the result in the variable f2 :
f2=${f1/pcw3po_cl.evt/pcw3po_cl_bary4.evt}
The following command executes : ls sw*sao.fits.gz inside the directory output001 and puts the result in the variable f3 :
f3=$(ls sw*sao.fits.gz)
The following command :
echo -e "$f1 $f2 $f3" | barycorr ra=253.467570 dec=39.760169
prints out the values of the 3 variables f1 f2 and f3 (which hopefully contains the file of the directory whose name matches (successively) swpcw3po_cl.evt, swpcw3po_cl_bary4.evt, and sw*sao.fits.gz
Then it "pipes" these values into your application named barycorr as standard input of this application (so barycorr can read these filenames and probably process the files).
This question already has answers here:
Is there a way to get the git root directory in one command?
(22 answers)
Closed 2 years ago.
I'm attempting to find the "root" of a folder. I'm doing this in a Bash script with the following (at least in my head):
# Get current directory (e.g. /foo/bar/my/subdir)
CURR_DIR = `cwd`
# Break down into array of folder names
DIR_ARRAY=(${CURR_DIR//\// })
# Iterate over items in DIR_ARRAY starting with "subdir"
<HELP WITH FOR LOOP SYNTAX>
# Each loop:
# build path to current item in DIR_ITER; e.g.
# iter N: DIR_ITER=/foo/bar/my/subdir
# iter N-1: DIR_ITER=/foo/bar/my
# iter N-2: DIR_ITER=/foo/bar
# iter 0: DIR_ITER=/foo
# In each loop:
# get the contents of directory using "ls -a"
# look for .git
# set ROOT=DIR_ITER
export ROOT
I've Googled for looping in Bash but it all uses the "for i in ARRAY" form, which doesn't guarantee reverse iteration order. What's the recommended way to achieve what I want to do?
One idea on reverse index referencing.
First our data:
$ CURR_DIR=/a/b/c/d/e/f
$ DIR_ARRAY=( ${CURR_DIR//\// } )
$ typeset -p DIR_ARRAY
declare -a DIR_ARRAY=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f")
Our list of indices:
$ echo "${!DIR_ARRAY[#]}"
0 1 2 3 4 5
Our list of indices in reverse:
$ echo "${!DIR_ARRAY[#]}" | rev
5 4 3 2 1 0
Looping through our reverse list of indices:
$ for i in $(echo "${!DIR_ARRAY[#]}" | rev)
do
echo $i
done
5
4
3
2
1
0
As for working your way up the directory structure using this 'reverse' index strategy:
$ LOOP_DIR="${CURR_DIR}"
$ for i in $(echo "${!DIR_ARRAY[#]}" | rev)
do
echo "${DIR_ARRAY[${i}]}:${LOOP_DIR}"
LOOP_DIR="${LOOP_DIR%/*}"
done
f:/a/b/c/d/e/f
e:/a/b/c/d/e
d:/a/b/c/d
c:/a/b/c
b:/a/b
a:/a
Though we could accomplish the same thing a) without the array and b) using some basic parameter expansions, eg:
$ LOOP_DIR="${CURR_DIR}"
$ while [ "${LOOP_DIR}" != '' ]
do
subdir="${LOOP_DIR##*/}"
echo "${subdir}:${LOOP_DIR}"
LOOP_DIR="${LOOP_DIR%/*}"
done
f:/a/b/c/d/e/f
e:/a/b/c/d/e
d:/a/b/c/d
c:/a/b/c
b:/a/b
a:/a
You can use dirname in a loop, to find the parent folder, then move up until you e.g., find the .git folder.
Quick example:
#!/usr/bin/env bash
set -eu
for arg in "$#"
do
current=$arg
while true
do
if [ -d "$current/.git" ]
then
echo "$arg: .git in $current"
break
fi
parent="$(dirname "$current")"
if [ "$parent" == "$current" ]
then
echo "No .git in $arg"
break
fi
current=$parent
done
done
For each parameter you pass to this script, it will print where it found the .git folder up the directory tree, or print an error if it didn't find it.
I have a file and contents are like :
|T1234
010000000000
02123456878
05122345600000000000000
07445678920000000000000
09000000000123000000000
10000000000000000000000
.T1234
|T798
013457829
0298365799
05600002222222222222222
09348977722220000000000
10000057000004578933333
.T798
Here one complete batch means it will start from |T and end with .T.
In the file i have 2 batches.
I want to edit this file to delete a batch for record 10(position1-2),if from position 3 till position 20 is 0 then delete the batch.
Please let me know how i can achieve this by writing a shell script or syncsort or sed or awk .
I am still a little unclear about exactly what you want, but I think I have it enough to give you an outline on a bash solution. The part I was unclear on is exactly which line contained the first two characters of 10 and remaining 0's, but it looks like that is the last line in each batch. Not knowing exactly how you wanted the batch (with the matching 10) handled, I have simply written the remaining wanted batch(es) out to a file called newbatch.txt in the current working directory.
The basic outline of the script is to read each batch into a temporary array. If during the read, the 10 and 0's match is found, it sets a flag to delete the batch. After the last line is read, it checks the flag, if set simply outputs the batch number to delete. If the flag is not set, then it writes the batch to ./newbatch.txt.
Let me know if your requirements are different, but this should be fairly close to a solution. The code is fairly well commented. If you have questions, just drop a comment.
#!/bin/bash
ifn=${1:-dat/batch.txt} # input filename
ofn=./newbatch.txt # output filename
:>"$ofn" # truncate output filename
declare -i bln=0 # batch line number
declare -i delb=0 # delete batch flag
declare -a ba # temporary batch array
[ -r "$ifn" ] || { # test input file readable
printf "error: file not readable. usage: %s filename\n" "${0//*\//}"
exit 1
}
## read each line in input file
while read -r line || test -n "$line"; do
printf " %d %s\n" $bln "$line"
ba+=( "$line" ) # add line to array
## if chars 1-2 == 10 and chars 3 on == 00...
if [ ${line:0:2} == 10 -a ${line:3} == 00000000000000000000 ]; then
delb=1 # set delete flag
fi
((bln++)) # increment line number
## if the line starts with '.'
if [ ${line:0:1} == '.' ]; then
## if the delete batch flag is set
if [ $delb -eq 1 ]; then
## do nothing (but show batch no. to delete)
printf " => deleting batch : %s\n" "${ba[0]}"
## if delb not set, then write the batch to output file
else
printf "%s\n" ${ba[#]} >> "$ofn"
fi
## reset line no., flags, and uset array.
bln=0
delb=0
unset ba
fi
done <"$ifn"
exit 0
Output (to stdout)
$ bash batchdel.sh
0 |T1234
1 010000000000
2 02123456878
3 05122345600000000000000
4 07445678920000000000000
5 09000000000123000000000
6 10000000000000000000000
7 .T1234
=> deleting batch : |T1234
0 |T798
1 013457829
2 0298365799
3 05600002222222222222222
4 09348977722220000000000
5 10000057000004578933333
6 .T798
Output (to newbatch.txt)
$ cat newbatch.txt
|T798
013457829
0298365799
05600002222222222222222
09348977722220000000000
10000057000004578933333
.T798
I have a very basic shell script here:
for file in Alt_moabit Book_arrival Door_flowers Leaving_laptop
do
for qp in 10 12 15 19 22 25 32 39 45 60
do
for i in 0 1
do
echo "$file\t$qp\t$i" >> psnr.txt
./command > $file-$qp-psnr.txt 2>> psnr.txt
done
done
done
command calculates some PSNR values and writes a detailed summary to a file for each combination of file, qp and i. That's fine.
The 2>> outputs one line of information that I really need. But when executed, I get:
Alt_moabit 10 0
total 47,8221 50,6329 50,1031
Alt_moabit 10 1
total 47,8408 49,9973 49,8197
Alt_moabit 12 0
total 47,0665 50,1457 49,6755
Alt_moabit 12 1
total 47,1193 49,4284 49,3476
What I want, however, is this:
Alt_moabit 10 0 total 47,8221 50,6329 50,1031
Alt_moabit 10 1 total 47,8408 49,9973 49,8197
Alt_moabit 12 0 total 47,0665 50,1457 49,6755
Alt_moabit 12 1 total 47,1193 49,4284 49,3476
How can I achieve that?
(Please feel free to change the title if you think there's a more appropriate one)
You could pass the -n option to your first echo command, so it doesn't output a newline.
As a quick demonstration, this :
echo "test : " ; echo "blah"
will get you :
test :
blah
With a newline between the two outputs.
While this, with a -n for the first echo :
echo -n "test : " ; echo "blah"
will get you the following output :
test : blah
Without any newline between the two output.
The (GNU version of) echo utility has a -n option to omit the trailing newline. Use that on your first echo. You'll probably have to put some space after the first line or before the second for readability.
You can use printf instead of echo, which is better for portability reasons.
printf is the correct way to solve your problem (+1 kurumi), but for completeness, you can also do:
echo "$file\t$qp\t$i $( ./command 2>&1 > $file-$qp-psnr.txt )" >> psnr.txt
While echo -n may work if you just want the print the output to console, it won't work if you want the output redirected to file.
If you want the concatenated output to be redirected to a file, this will work:
echo "Str1: `echo "Str2"`" >> file
I was also facing the same problem.
To define my problem, I have a script which is using the echo function like this:
echo -n "Some text here"
echo -n "Some text here"
The output I was getting is like this:
-n Some text here
-n Some text here
and I want the text to be in same line and it is also printing -n option in the output.
Note :- According to man Page, -n option do not print the trailing newline character.
The way I solved it using by adding the shebang in the starting of the script file like this.
#!/bin/bash
echo -n "Some text here"
echo -n "Some text here"
This will print the desired output like this:
Some text here Some text here
Hope this helps!