How to change content in variable based on pwd in bash? - bash

I would like to do following:
get all dependencies (dir names)
get basename of current directory
since current directory is not a dependency, get rid of it
print them
what I have so far (from bashrc):
export dep=$({ tmp=$(ls /usr/local/lib/node_modules/); echo ${tmp//$(basename $(pwd))/}; })
The goal is it to have it in variable, not a function or alias becuase I want to use it later (such as for npm link $dep), which I would not be able if it was function.
But the current output DOES include the current directory. Was it invoked from the current dir, the current dir would not be included. So I guess the variable is not reexecuted to take into account it changed its dir (from where bashrc is, to where I am now).
So how to make it to NOT include the current dir?

A variable is simply static text, the shell (or, let alone, the string itself) in no way keeps track of how its value was calculated; it's just a sequence of characters. If you need the value to change depending on external circumstances, you need to assign it again in those circumstances, or simply use a script or a function instead of a variable.
Here is a simple function which avoids trying to parse the output from ls:
getdep () {
( cd /usr/local/lib/node_modules
printf '%s\n' * ) |
grep -vFx "$(basename "$(pwd)")"
}
You would call it like
dep=$(getdep)
when you need to update dep, or simply use $(getdep) instead of $dep.

Related

Perl code doesn't run in a bash script with scheduling of crontab

I want to schedule my Perl code to be run every day at a specific time. so I put the below code in bash file:
Automate.sh
#!/bin/sh
perl /tmp/Taps/perl.pl
The schedule has been specified in below path:
10 17 * * * sh /tmp/Taps/Automate.sh > /tmp/Taps/result.log
When the time arrived to 17:10 the .sh file hasn't been running. however, when I run ./Automate.sh (manually) it is running and I see the result. I don't know what is the problem.
Perl Code
#!/usr/bin/perl -w
use strict;
use warnings;
use Data::Dumper;
use XML::Dumper;
use TAP3::Tap3edit;
$Data::Dumper::Indent=1;
$Data::Dumper::Useqq=1;
my $dump = new XML::Dumper;
use File::Basename;
my $perl='';
my $xml='';
my $tap3 = TAP3::Tap3edit->new();
foreach my $file(glob '/tmp/Taps/X*')
{
$files= basename($file);
$tap3->decode($files) || die $tap3->error;
}
my $filename=$files.".xml\n";
$perl = $tap3->structure;
$dump->pl2xml($perl, $filename);
print "Done \n";
error:
No such file or directory for file X94 at /tmp/Taps/perl.pl line 22.
X94.xml
foreach my $file(glob 'Taps/X*') -- when you're running from cron, your current directory is /. You'll want to provide the full path to that Taps directory. Also specify the output directory for Out.xml
Cron uses a minimal environment and a short $PATH, which may not necessarily include the expected path to perl. Try specifying this path fully. Or source your shell settings before running the script.
There are a lot of things that can go wrong here. The most obvious and certain one is that if you use a glob to find the file in directory "Taps", then remove the directory from the file name by using basename, then Perl cannot find the file. Not quite sure what you are trying to achieve there. The file names from the glob will be for example Taps/Xfoo, a relative path to the working directory. If you try to access Xfoo from the working directory, that file will not be found (or the wrong file will be found).
This should also (probably) lead to a fatal error, which should be reported in your error log. (Assuming that the decode function returns a false value upon error, which is not certain.) If no errors are reported in your error log, that is a sign the program does not run at all. Or it could be that decode does not return false on missing file, and the file is considered to be empty.
I assume that when you test the program, you cd to /tmp and run it, or your "Taps" directory is in your home directory. So you are making assumptions about where your program looks for the files. You should be certain where it looks for files, probably by using only absolute paths.
Another simple error might be that crontab does not have permission to execute the file, or no read access to "Taps".
Edit:
Other complications in your code:
You include Data::Dumper, but never actually use that module.
$xml variable is not used.
$files variable not declared (this code would never run with use strict)
Your $files variable is outside your foreach loop, which means it will only run once. Since you use glob I assumed you were reading more than one file, in which case this solution will probably not do what you want. It is also possible that you are using a glob because the file name can change, e.g. X93, X94, etc. In that case you will read the last file name returned by the glob. But this looks like a weak link in your logic.
You add a newline \n to a file name, which is strange.

Passed variable is incorrect

I wish to pass a variable patientid from a mother script to a sub-script. The variable should correspond to the names of the folders (one at a time) in the pertinent directory.
The mother script appears as follows:
#!/bin/sh
set verbose
# (1) have folder that contains individual patient ID folders
# (2) do for loop at at beginning of script:
counter=0
for folder in /Directory/*
do
cd $folder
#Obtain Patient ID as variable
patientid=`basename $folder`
#Pass "patientid" variable to each script you are running
/Directory/subscript.sh $patientid
done
Then, in the subscript, the variable is passed as follows:
#!/bin/sh
set verbose
patientid=$1
cd /Directory/$patientid
###etc.
The problem is, in the output, patientid comes out as verbose (i.e. the directory that is used is /Directory/verbose when it should contain the name of the folder from that original directory). Any idea what the problem is?
set verbose
Either delete this line or change it to:
set -o verbose
When you write set verbose that changes $1 to the string verbose, which explains why $patientid ends up set to verbose.

How do I loop through a directory path stored in a variable

I'm teaching my self bash and trying to create a script that will loop through the directories contained within a given directory (or the current directory, if none is supplied).
This is the script I have so far:
#!/bin/bash
start_dir=${1:-`pwd`} # set to current directory or user supplied directory
echo start_dir=$start_dir
for d in $start_dir ; do
echo dir=$d
done
First, all this script currently does is sets d to start_dir abd echos the value in start_dir. I guess this makes sense, but I was hoping it would actually loop through the directory. How do I get this to actually loop through the directory set in the start_dir variable?
Also, I'm wanting to only loop through the directories. This answer, shows that putting a / after the path will ensure only directories are returned to the for loop. Is there a way to incorporate this to ensure looping over start_dir will only return directories, given that there is a possibility that the user will not provide a directory path to the script?
Cheers
The example uses a glob, and so can you:
#!/bin/bash
start_dir=${1:-`pwd`} # set to current directory or user supplied directory
echo "start_dir=$start_dir"
for d in "$start_dir"/*/ ; do
echo "dir=$d"
done
* is not a directory name, it just means "any string". Bash expands it to find all paths matching the pattern.

looping files with bash

I'm not very good in shell scripting and would like to ask you some question about looping of files big dataset: in my example I have alot of files with the common .pdb extension in the work dir. I need to loop all of them and i) to print name (w.o pdb extension) of each looped file and make some operation after this. E.g I need to make new dir for EACH file outside of the workdir with the name of each file and copy this file to that dir. Below you can see example of my code which are not worked- it's didn't show me the name of the file and didn't create folder for each of them. Please correct it and show me where I was wrong
#!/bin/bash
# set the work dir
receptors=./Receptors
for pdb in $receptors
do
filename=$(basename "$pdb")
echo "Processing of $filename file"
cd ..
mkdir ./docking_$filename
done
Many thanks for help,
Gleb
If all your files are contained within the .Repectors folder, you can loop each of them like so:
#!/bin/bash
for pdb in ./Receptors/*.pdb ; do
filename=$(basename "$pdb")
filenamenoextention=${filename/.pdb/}
mkdir "../docking_${filenamenoextention}"
done
Btw:
filenamenoextention=${filename/.pdb/}
Does a search replace in the variable $pdb. The syntax is ${myvariable/FOO/BAR}, and replaces all "FOO" substrings in $myvariable with "BAR". In your case it replaces ".pdb" with nothing, effectively removing it.
Alternatively, and safer (in case $filename contains multiple ".pdb"-substrings) is to remove the last four characters, like so: filenamenoextention=${filename:0:-4}
The syntax here is ${myvariable:s:e} where s and e correspond to numbers for the start and end index (not inclusive). It also let's you use negative numbers, which are offsets from the end. In other words: ${filename:0:-4} says: extract the substring from $filename starting from index 0, until you reach fourth-to-the-last character.
A few problems you have had with your script:
for pdb in ./Receptors loops only "./Receptors", and not each of the files within the folder.
When you change to parent directory (cd ..), you do so for the current shell session. This means that you keep going to the parent directory each time. Instead, you can specify the parent directory in the mkdir call. E.g mkdir ../thedir
You're looping over a one-item list, I think what you wanted to get is the list of the content of ./Receptors:
...
for pdb in $receptors/*
...
to list only file with .pdb extension use $receptors/*.pdb
So instead of just giving the path in for loop, give this:
for pdb in $receptors/*.pdb
To remove the extension :
set the variable ext to the extension you want to remove and using shell expansion operator "%" remove the extension from your filename eg:
ext=.pdb
filename=${filename%${ext}}
You can create the new directory without changing your current directory:
So to create a directory outside your current directory use the following command
mkdir ../docking_$filename
And to copy the file in the new directory use cp command
After correction
Your script should look like:
receptors=./Receptors
ext=.pdb
for pdb in $receptors/*.pdb
do
filename=$(basename "$pdb")
filename=${filename%${ext}}
echo "Processing of $filename file"
mkdir ../docking_$filename
cp $pdb ../docking_$filename
done

Variable name appears in prompt

I'm trying to automate my daily task of opening my editor and "cd-ing" into a project folder at the same time by typing 'project something'. So far so good. The code is working. But I got some unexpected behavior. The current directory label is showing Lukas-mbp:~sub_directory(2603m|master) $. What is this sub_directory doing there. It is the variable name I'm using, as the code below. But can anyone tell why my terminal is showing that instead of the actual directory?
function project() {
for directory in ~/projects/*
do
for sub_directory in $directory/*
do
if [[ "$sub_directory" =~ $1 ]]; then
cd "$sub_directory"
sublime $sub_directory
return
fi
done
done
}
Use a simple wildcard instead of doing all that unecessary looping! I've not, however, completely understood your subdirectories tree, so I've came with two alternatives, use whichever is appropriated. Add the function to ~/.bashrc.
1 - If your subdirectories tree is like this (works exactly as in your example*):
~/projects/subdir1/target1/
~/projects/subdir1/target2/
~/projects/subdir2/target3/
~/projects/subdir2/target4/
The function will be like this:
function project {
cd ~/projects/*/"$1"
sublime .
# or sublime ./*
}
* Keep in mind that if you have two or more target with the same name, but on different subdir, it will only match the first one, as will your example.
2 - If your subdirectories tree is like this (which seems more common to me):
~/projects/target1/
~/projects/target2/
~/projects/target3/
~/projects/target4/
The function will be like this:
function project {
cd ~/projects/"$1"
sublime .
# or sublime ./*
}

Resources