Passing a directory in bash trough pipe communication - bash

This problem is a homework, I know there would be much easier ways of solving this problem, but it is what it is.
The question goes as follows:
-I have a bash script that does some creates some files.
-I have to pass this first argument of this script ( which is a directory ) to another script trough a pipeline ( | ).
The problem comes in when I do try and pass this directory as an argument, in the other script, the argument I receive is null.
This is the whole code, in this exact order.
Nothing was left out.
The first script receives at least 11 argument:
if [[ $# -lt 11 ]]; then
echo "Not enough arguments." >&2;
exit 1;
fi;
The script will check if the first argument is an actual directory:
if [[ ! -d $1 ]]; then
echo "Not a valid first argument." >&2;
exit 1;
fi;
Here, I'll save my first directory here so that I can shift, and then I'm going to declare more stuff that i need
Directory=$1;
shift;
N=$#
name_array=("$#")
Pos=0;
Afterwards, the script will use all the other arguments to create .txt files with those names, and add a number of non-null lines in each .txt file ( So, argument2.txt has N-1 lines, argument3.txt has N-2 lines ... argumentN.txt has 1 line.) Afterwards, I have to change their permissions to 600.
while [[ $# -gt 0 ]]; do
if [[ -a "$Directory"/"$1".txt ]]; then
rm "$Directory"/"$1".txt;
fi
touch "$Directory"/"$1".txt
for((i=0;i<N;i++)); do
echo "$N" >> "$Directory"/"$1".txt;
done;
chmod u=rw "$Directory"/"$1".txt;
N=$(($N-1));
name_array["$Pos"]="$Directory"/"$1".txt;
Pos=$(($Pos + 1));
shift;
done;
Problem comes here, I'm trying to echo the first directory..
echo $Directory;
I have tried this as well, with the same results as the one above
echo "$(cd "$(dirname "$Directory")" && pwd -P)/$(basename "$Directory")";
.. like this so I can use in the Shell the pipeline commands.
The second script will receive the directory, enter it and search trough all the files that have been created just now.
In the Unix terminal, I use this:
./FirstScript.bash 1 2 3 4 5 6 7 8 9 10 11 12 | ./SecondScript.bash
Thank you in advance!

Thanx for being honest, and of course we're not going to do your homework, but,...
Most of the code seems pretty good. There are some smaller issues, which you might easily solve with shellcheck (if it isn't installed, install it or use the web version), and I would not put ; at the end of each line.
So, instead of running the complete pipeline at once, break it down to find where your problem lies. If you run
./FirstScript.bash 1 2 3 4 5 6 7 8 9 10 11 12
If the output is as expected (so, with the echo $Directory), and the files are created, then the first script is good. So, in this case, the directory 1 will contain the files 2 3 4 5 6 7 8 9 10 11 12, and the output will be 1.
Then, you will run the second script
./SecondScript.bash
and as input you will give:
1
^D
(that is control-d on Unix). And then the second script should do what you want (or not).
In this way you can debug the scripts separately.
(quick face-palm question: is 1 really the directory you want, or did you just forget the directory name as first argument?)

Related

Batch rename files with iteration

I have an experiment with 60 plots. I took 5 photos per treatment with a go-pro, plus a 6th photo of the sky to mark the point where I moved to the next plot (Total 360 photos). How could I write a bash script to rename these files automatically; i.e
Change the set of files:
GOPRO0001.jpg, GOPRO0002.jpg, ... , ... , GOPRO0360.jpg
Into something like:
plot1_1.jpg, ... , plot1_6.jpg, ..., ..., plot60_1.jpg, ... , plot60_6.jpg
What's the most efficient way of doing this? My thinking is I need to have 2 levels of iteration, but I'm not sure how to do it..
You can try this BASH script:
#!/bin/bash
i=1;j=1
for file in GOPRO*.jpg; do
mv "$file" "plot${j}_${i}.jpg"
if [[ $i -eq 6 ]]; then
i=1
((j++))
else
((i++))
fi
done
If you want to verify what it would do before actually letting it take action, put an echo before the mv command.

Shell script to rsync a file every week without cronjob (school assignement)

#!/bin/bash
z=1
b=$(date)
while [[ $z -eq 1 ]]
do
a=$(date)
if [ "$a" == "$b" ]
then
b=$(date -d "+7 days")
rsync -v -e ssh user#ip_address:~/sample.tgz /home/kartik2
sleep 1d
fi
done
I want to rsync a file every week !! But if I start this script on every boot the file will be rsynced every time the system starts !! How to alter the code to satisfy week basis rsync ? ( PS- I don't want to do this through cronjob - school assignment)
You are talking about having this run for weeks, right? So, we have to take into account that the system will be rebooted and it needs to be run unattended. In short, you need some means of ensuring the script is run at least once every week even when no one is around. The options look like this
Option 1 (worst)
You set a reminder for yourself and you log in every week and run the script. While you may be reliable as a person, this doesn't allow you to go on vacation. Besides, it goes against our principle of "when no one is around".
Option 2 (okay)
You can background the process (./once-a-week.sh &) but this will not reliable over time. Among other things, if the system restarts then your script will not be operating and you won't know.
Option 3 (better)
For this to be reliable over weeks one option is to daemonize the script. For a more detailed discussion on the matter, see: Best way to make a shell script daemon?
You would need to make sure the daemon is started after reboot or system failure. For more discussion on that matter, see: Make daemon start up with Linux
Option 4 (best)
You said no cron but it really is the best option. In particular, it would consume no system resources for the 6 days, 23 hours and 59 minutes when it does not need to running. Additionally, it is naturally resilient to reboots and the like. So, I feel compelled to say that creating a crontab entry like the following would be my top vote: #weekly /full/path/to/script
If you do choose option 2 or 3 above, you will need to make modifications to your script to contain a variable of the week number (date +%V) in which the script last successfully completed its run. The problem is, just having that in memory means that it will not be sustained past reboot.
To make any of the above more resilient, it might be best to create a directory where you can store a file to serve as a semaphore (e.g. week21.txt) or a file to store the state of the last run. Something like once-a-week.state to which you would write a value when run:
date +%V > once-a-week.state # write the week number to a file
Then to read the value, you would:
file="/path/to/once-a-week.state" # the file where the week number is stored
read -d $'\x04' name < "$file"
echo "$name"
You would then need to check to see if the week number matched this present week number and handle the needed action based on match or not.
#!/bin/bash
z=1
b=$(cat f1.txt)
while [[ $z -eq 1 ]]
do
a=$(date +"%d-%m-%y")
if [ "$a" == "$b" ] || [ "$b" == "" ] || [$a -ge $b ]
then
b=$(date +"%d-%m-%y" -d "+7 days")
echo $b > f1.txt
rsync -v -e ssh HOST#ip:~/sample.tgz /home/user
if [ $? -eq 0 ]
then
sleep 1d
fi
fi
done
This code seems to works well and good !! Any changes to it let me know

shell script for creating multiple image files ascending order

so the thing is, I'm trying to code a shell script that creates multiple image files in an ascending order, but, the ones with only one digit must come with a zero first. I'm a beginner and searching throughout the internet I've found that there are some syntax differences when it comes to the indexing system, so on my computer the following "for" syntax works fine, but I'm sure there's a better approach to that. there it goes:
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
do
if (${i:0:1} == "0") then
touch "0${i}.jpg"
else
touch "$i.jpg"
fi
done
it returns "bad substitution". is there anything wrong with this approach?
for i in {0..15}; do printf -v name "%02i.jpg" $i; touch "$name" ; done
Or, if it is easier to read:
for i in {0..15}; do
printf -v name "%02i.jpg" $i
touch "$name"
done
When this is done, the following files are created:
$ ls
00.jpg 01.jpg 02.jpg 03.jpg 04.jpg 05.jpg 06.jpg 07.jpg 08.jpg 09.jpg 10.jpg 11.jpg 12.jpg 13.jpg 14.jpg 15.jpg
Explanation
for i in {0..15}; do
This starts the loop.
printf -v name "%02i.jpg" $i
This creates a shell variable name which has the desired format. %02i instructs printf to write the number out in two space with zero-padding.
touch "$name"
This creates the file
done
This signals the end of the loop.
Alternative
Starting from the script in the question, a couple changes are needed on the if statement. The following works:
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
do
if [ "$i" -le 9 ]; then
touch "0${i}.jpg"
else
touch "$i.jpg"
fi
done

Output of command in Bash script to Drop-down box?

First off, I appreciate any and all help in answering this question.
I have a command in a bash script that will output the following:
255 254 253 252 ... 7 6 5 4 3 2 1
It is a specific list of numbers, beginning with the largest (which is what I would like), then going to the smallest. The dataset is space-delimited. The output above (except including all numbers), is what you would see if you ran this command in the terminal on a linux machine, or through a bash script.
I have configured my apache2 server to allow for cgi/bash through the cgi-bin directory. When I run this command in a bash file from the web, I get the expected output.
What I'm looking for is for a way to be able to put these numbers each as a separate entry in a drop-down box for selection, meaning the user can select one point of data (254, for example) from the drop down menu.
I'm not sure what I'm doing with this, so any help would be appreciated. I'm not sure if I need to convert the data into an array, or what. The drop down menu can be on the same page of the bash script, but wherever it is, it has to update it's list of numbers from the command every time it is run.
Thank you for your help.
I've always found this site useful when fiddling with shell scripts: http://tldp.org/LDP/abs/html/
you'll have to get your output into an array using some sort of string manipulation using the spaces as delimiters, then loop over that to build some html output - so the return value will basically just output your select box on the page where you execute your cgi/bash script.
-sean
Repeating the answer (since the original question was marked as duplicate):
you can write a bash for loop to do everything. This just prints out the elements:
for i in `seq 1 "${#x[*]}"`; do
echo "|${x[i]} |"
done
To get the alignment correct, you need to figure out the max length (one loop) and then print out the terms:
# w will be the length
w=0
for i in `seq 1 "${#x[*]}"`; do
if [ $w -lt ${#x[$i]} ]; then w=${#x[$i]}; fi
done
for i in `seq 1 $((w+2))`; do printf "%s" "-"; done
printf "\n"
for i in `seq 1 "${#x[*]}"`; do
printf "|%-$ws |\n" ${#x[$i]}
done
for i in `seq 1 $((w+2))`; do printf "%s" "-"; done
printf "\n"

Change file name suffix(es) (using sed ?)

I'd like to change the file name suffix from files (using a bash script), but sometimes there are files with one period and some with two.
Now I use this:
new_file=`echo ${file} | sed 's/\(.*\.log.*\)'${suf}'/\1.'${num}'/'`
Where 'new_file' is the new file name, 'file' the original file name, '${suf}' the file's suffix and ${num} a new number.
So some.log must become some.log.1 and some.log.1 must become some.log.2. With my code some.log becomes some.log.1, but some.log.1 remains some.log.1.
I hope I'm clear enough. I appreciate any advice (even not using sed).
Update:
#paxdiablo. Something went wrong testing I think.
Now I use this piece of code as test;
#!/usr/bin/bash
shft() {
for suff in {6..1} ; do
if [[ -f "$1.${suff}" ]] ; then
((nxt = suff + 1))
echo Moving "$1.${suff}" to "$1.${nxt}"
mv -f "$1.${suff}" "$1.${nxt}"
fi
done
echo Moving "$1" to "$1.1"
mv -f "$1" "$1.1"
}
clear
folder=~/logs/*.log
for i in {1..20}; do
echo ${i}> ~/logs/some.log
for fspec in ${folder} ; do
shft "${fspec}"
done
done
Every thing works fine now. Sorry for the confusion.
If you're looking to roll over log files, and depending on how complex you need to get, I've used the following segment before:
#!/usr/bin/bash
# rollover.sh
# Rolls over log files in the current directory.
# *.log.8 -> *.log.9
# *.log.7 -> *.log.8
# : : :
# *.log.1 -> *.log.2
# *.log -> *.log.1
shft() {
# Change this '8' to one less than your desired maximum rollover file.
# Must be in reverse order for renames to work (n..1, not 1..n).
for suff in {8..1} ; do
if [[ -f "$1.${suff}" ]] ; then
((nxt = suff + 1))
echo Moving "$1.${suff}" to "$1.${nxt}"
mv -f "$1.${suff}" "$1.${nxt}"
fi
done
echo Moving "$1" to "$1.1"
mv -f "$1" "$1.1"
}
for fspec in *.log ; do
shft "${fspec}"
#date >"${fspec}" #DEBUG code
done
This will automatically roll over log files up to version 9 although you can just change the suff for loop to allow more.
With that DEBUG added so new files are created automatically for testing, the following transcript shows it in action:
pax> touch qq.log ; ./rollover.sh
Moving "qq.log" to "qq.log.1"
pax> touch "has spaces.log" ; ./rollover.sh
Moving "has spaces.log" to "has spaces.log.1"
Moving "qq.log.1" to "qq.log.2"
Moving "qq.log" to "qq.log.1"
pax> ll *log*
-rw-r--r-- 1 pax None 30 2010-09-11 20:39 has spaces.log
-rw-r--r-- 1 pax None 0 2010-09-11 20:39 has spaces.log.1
-rw-r--r-- 1 pax None 30 2010-09-11 20:39 qq.log
-rw-r--r-- 1 pax None 30 2010-09-11 20:38 qq.log.1
-rw-r--r-- 1 pax None 0 2010-09-11 20:38 qq.log.2
The good thing about this script is that it's easily configurable to handle a large amount of history (by changing the {8..1} bit), handles names with spaces, and handles gaps relatively robustly if log files go missing.
To rotate logs, you should really use logrotate.
If you can't rely on logrotate being available, here's a way do it inside the shell. To keep things simple, I'll assume that nothing else (including another instance of your script) will try to rename the log files while your script is running.
The easiest approach is to recursively rename log N+1 before actually renaming log N to N+1. The shell can perform all the necessary arithmetic, you don't need sed here. Note that while recursive functions are possible in a POSIX shell, there are no local variables other than the positional parameters (many shells offer local variables as an extension).
#!/bin/sh
## Move "$1.$2" to "$1.$(($2+1))", first rotating the target as well.
rotate () {
if [ -e "$1.$(($2+1))" ]; then rotate "$1" $(($2+1)); fi
mv -- "$1.$2" "$1.$(($2+1))"
}
for x; do
## Break each argument into FILE.NUMBER or just FILE.
suffix=${x##*.}
case $suffix in
*[!0-9]*)
if [ -e "$x.0" ]; then rotate "$x" 0; fi
mv -- "$x" "$x.0";;
*) rotate "${x%.*}" "$suffix";;
esac
done
Regarding what you've written, note that echo ${file} is bad for two reasons: most importantly, if ${file} contains any special characters such as white space, the shell will interpret them; also, with some shells, echo itself will interpret backslashes and possibly a leading -. So you should always write printf %s "$file" instead.

Resources