Manage Unix rights reading dynamic field in a csv file in bash - bash

i'm currently stuck with a bash script that should be able to manage permission on files and directories at the end of the processing part.
Actually i have 4 components:
the main script which source .conf file and libs (.sh), process things, and call a function "ApplyPermissionFromCSVFile" with a .csv file as argument at the end, to ensure that rights are correctly set. This function should handle the job for managing permission on files
a script called "permission_lib.sh" which contains several functions, including the "ApplyPermissionFromCSVFile" one. This script is SOURCED at the begining of main script
a .conf file which contains some path defined as variables which is SOURCED at the beginning of main script
a .csv file containing paths (including "dynamic path", which refer to variables defined in conf file) for files and directories which is READ by the ApplyPermissionFromCSVFile function
At the moment, the main script runs correctly, is able to source both conf file and lib file but when i put some debug point inside the "ApplyPermissionFromCSVFile", it appears that "dynamic path" is not interpreted by bash.
Extract of Main Script:
#########################################
includes
##################################################
# this section can _almost_ be copied as-is ;-)
nameOfThisScript=$(basename "${BASH_SOURCE[0]}")
directoryOfThisScript="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
configFile="$directoryOfThisScript/$nameOfThisScript.conf"
functionsFile="$directoryOfThisScript/safeScriptsFunctions.sh"
permissionLib="$directoryOfThisScript/permission_lib.sh"
permissionFile="$directoryOfThisScript/$nameOfThisScript.permissionFile.csv"
for fileToSource in "$configFile" "$functionsFile" "$permissionLib"; do
source "$fileToSource" 2>/dev/null || {
echo "File '$fileToSource' not found"
exit 1
}
done
#Main Loop to read CSV File is called in permissionLib.sh
ApplyPermissionFromCSVFile $permissionFile
Extract of conf file (real filename replaced for exemple):
totovariable="/usr/local"
tatavariable="$totovariable/bin"
Extract of csv file:
$totovariable;someuser;somegroup;0600
$tatavariable;someuser;somegroup;0600
Extract of permission lib file:
function ApplyPermissionFromCSVFile {
local csvFileName="$1"
local fieldNumberFileName=1
local fieldNumberOwner=2
local fieldNumberGroupOwner=3
local fieldNumberPermissions=4
while read csvLine
do
fileName=$(getFieldFromCsvLine "$fieldNumberFileName" "$csvLine")
fileOwner=$(getFieldFromCsvLine "$fieldNumberOwner" "$csvLine")
fileGroupOwner=$(getFieldFromCsvLine "$fieldNumberGroupOwner" "$csvLine")
filePermissions=$(getFieldFromCsvLine "$fieldNumberPermissions" "$csvLine")
permissionArray[0,0]="$fileName|$fileOwner|$fileGroupOwner|$filePermissions"
echo "${permissionArray[0,0]}"
done < "$csvFileName"
}
getFieldFromCsvLine() {
csvFieldSeparator=';'
fieldNumber="$1"
csvLine="$2"
echo "$csvLine" | cut -d "$csvFieldSeparator" -f "$fieldNumber"
}
Don't bother for the fact that the loop overwrite value at each iterations, it's not the purpose here (but optionnal answer :p).
Which output results:
$totovariable|someuser|somegroup|0600
$tatavariable|someuser|somegroup|0600
Changing owner to someuser:somegroup for file $tatavariable
chown: cannot access '$tatavariable': No such file or directory
Changing permissions to 0600 for file $tatavariable
chmod: cannot access '$tatavariable': No such file or directory
After some investigations an research, it seems normal as:
conf file is SOURCED (by main script)
lib file is SOURCED (by main script)
csv file is not SOURCED but READ (by function in lib). So bash consider the variables contents as "pure-string", and not variables
The issue, is that i can't clearly see how and where i should replace the "pure-string" variable by its value (defined in .conf file and sourced by main script): at the main script level ? at the lib level with global variables ?
Actual solutions that i've found:
sed substitute
use eval
Any help could be appreciated.

Solution used:
fileName=eval echo "$fileName"
As the pathes in .csv file may contain "$" Symbol, Bash parameter expansion will not work in every case.
Exemple:
with the following csv content:
$tatavariable;someuser;somegroup;0600
$totovariable/thisotherfile.txt;someuser;somegroup;0660
$totovariable;someuser;somegroup;0600
/home/someuser/lolzy.txt;someuser;somegroup;0666
And the following conf file:
totovariable="/home/someuser/fack"
tatavariable="$totovariable/thisfile.txt"
The following bash code (based on eval which is not highly recommended in most case) will work on every case (containing $ symbol or not):
#!/bin/bash
#########################
# Function
#########################
getFieldFromCsvLine() {
local csvFieldSeparator=';'
local fieldNumber="$1"
local csvLine="$2"
echo "$csvLine" | cut -d "$csvFieldSeparator" -f "$fieldNumber"
}
#########################
#Core Script
#########################
source configFile.conf
csvFileName="permissionFile.csv"
fieldNumberFileName=1
fieldNumberOwner=2
fieldNumberGroupOwner=3
fieldNumberPermissions=4
while read csvLine
do
fileName=$(getFieldFromCsvLine "$fieldNumberFileName" "$csvLine")
fileOwner=$(getFieldFromCsvLine "$fieldNumberOwner" "$csvLine")
fileGroupOwner=$(getFieldFromCsvLine "$fieldNumberGroupOwner" "$csvLine")
filePermissions=$(getFieldFromCsvLine "$fieldNumberPermissions" "$csvLine")
#Managing Variables used as 'Dynamic path'
fileName=$(eval echo "$fileName")
echo "Content of \$fileName is $fileName"
done < "$csvFileName"
Results:
[someuser#SAFEsandbox:~]$ ./simpletest.sh
Content of $fileName is /home/someuser/fack/thisfile.txt
Content of $fileName is /home/someuser/fack/thisotherfile.txt
Content of $fileName is /home/someuser/fack
Content of $fileName is /home/someuser/lolzy.txt
Whereas the following bash code (base on bash parameters expansion) will throw errors:
#!/bin/bash
#########################
# Function
#########################
getFieldFromCsvLine() {
local csvFieldSeparator=';'
local fieldNumber="$1"
local csvLine="$2"
echo "$csvLine" | cut -d "$csvFieldSeparator" -f "$fieldNumber"
}
#########################
#Core Script
#########################
source configFile.conf
csvFileName="permissionFile.csv"
fieldNumberFileName=1
fieldNumberOwner=2
fieldNumberGroupOwner=3
fieldNumberPermissions=4
while read csvLine
do
fileName=$(getFieldFromCsvLine "$fieldNumberFileName" "$csvLine")
fileOwner=$(getFieldFromCsvLine "$fieldNumberOwner" "$csvLine")
fileGroupOwner=$(getFieldFromCsvLine "$fieldNumberGroupOwner" "$csvLine")
filePermissions=$(getFieldFromCsvLine "$fieldNumberPermissions" "$csvLine")
#Managing Variables used as 'Dynamic path'
fileName=${!fileName}
echo "Content of \$fileName is $fileName"
done < "$csvFileName"
Behaviour example when .csv contains $ symbol:
[someuser#SAFEsandbox:~]$ ./simpletest.sh
./simpletest.sh: line 35: $tatavariable: bad substitution
Behaviour example when you remove the $ symbol in .csv file, but there is still incremental path notions in it:
[someuser#SAFEsandbox:~]$ ./simpletest.sh
Content of $fileName is /home/someuser/fack/thisfile.txt
./simpletest.sh: line 35: totovariable/thisotherfile.txt: bad substitution
Using bash parameter expansion (which is recommended in most case) is not easy here, because it forces to manage 2 cases in the script:
path containing a leading $ or serveral $ (contatenated variables paths)
path not containing a leading $ or serveral $ (contatenated variables paths)
Regards

Related

Bash check for file

I have this piece of code to check if a file exists in a directory, but it exits saying the file doesn't exist -
# in another file in the same directory
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "${SCRIPT_DIR}/common.sh" # calling the code below
BACKUP_VARS_FILE=${BACKUP_VARS_FILE:-"${SCRIPT_DIR}"/bitbucket.diy-backup.vars.sh}
if [ -f "${BACKUP_VARS_FILE}" ]; then
source "${BACKUP_VARS_FILE}"
echo "Using vars file: '${BACKUP_VARS_FILE}'"
else
error "'${BACKUP_VARS_FILE}' not found"
bail "You should create it using '${SCRIPT_DIR}/bitbucket.diy-backup.vars.sh.example' as a template"
fi
But the file does exist in the same folder as common.sh where the code is.
Output - it doesn't even go in the if/else block
[![enter image description here][2]][2]
Any help please?
edit -
Variable output, reading the file (with less) works -
[![enter image description here][3]][3]
Since the output is "/bitbucket...", without anything before the slash, I think your ${SCRIPT_DIR} is empty, which makes it looking for the file laying under root directory /.
Your current directory is /home/ubuntu/backup, where can find bitbucket.diy-backup.vars.sh, but your script may looking for /bitbucket.diy-backup.vars.sh, not /home/ubuntu/backup/bitbucket.diy-backup.vars.sh. Check ${SCRIPT_DIR} variable first.

FSL - Can I set the path within an fsf file (program setup file) to current working directory?

I am performing MRI analysis, and have written a script that loops through all scans for each subject, with the next step being to run a command called feat, which can be seen towards the end of the coding block below.
#! /bin/sh
path=~/rj82/james_folder/data_copy/
cd $path
# Loop through the MND and Control directories
for directory in * ; do
cd $path
cd $directory
# Loop through each subject in each directory
for subject in ??? ; do
cd $path/$directory/$subject
# Loop through each scan for each subject
for scan in MR?? ; do
cd $path/$directory/$subject/$scan
# Run feat on each scan
feat design.fsf
cd ..
done
done
done
You will also see feat takes a design.fsf file which sets the parameters for feat. To make this file I used one MRI scan as input data.
Below I have attached the regions within the design.fsf code that show the path of the files used to create the file.
# 4D AVW data or FEAT directory (1)
set feat_files(1) "/projects/rj82/james_folder/data_copy/mnd/002/MR02/fmri/fmri_data"
# Add confound EVs text file
set fmri(confoundevs) 0
# Session's alternate reference image for analysis 1
set alt_ex_func(1) "/projects/rj82/james_folder/data_copy/mnd/002/MR02/fmri_ref/fmri_ref_brain"
# B0 unwarp input image for analysis 1
set unwarp_files(1) "/projects/rj82/james_folder/data_copy/mnd/002/MR02/fmaps/fmap_rads"
# B0 unwarp mag input image for analysis 1
set unwarp_files_mag(1) "/projects/rj82/james_folder/data_copy/mnd/002/MR02/fmaps/mag_e1_brain"
# Subject's structural image for analysis 1
set highres_files(1) "/projects/rj82/james_folder/data_copy/mnd/002/MR02/t1/t1_brain"
If I run the script (first coding block) feat will run correctly, however as the path within the design.fsf file only refers to one scan, it will just continuously run feat on this single scan.
As the subdirectories and files within each subject have the same name, I want to replace the path "/projects/rj82/james_folder/data_copy/mnd/002/MR02 with the current directory from the script (first coding block), whilst keeping the end portion (e.g fmri/fmri_data") allowing me to loop through and run feat on each subject.
I have tried setting path=pwd and replacing the above path with "$path/fmri/fmri_data" which does not work, as well as removing the "/projects/rj82/james_folder/data_copy/mnd/002/MR02 portion entirely, as I hoped it would just use the current directory, but this also doesn't work. Error message is the same for both:
/bin/mkdir: cannot create directory ‘/fmri’: Permission denied
while executing
"fsl:exec "/bin/mkdir -p $FD" -n"
(procedure "firstLevelMaster" line 22)
invoked from within
"firstLevelMaster $session"
invoked from within
"if { $done_something == 0 } {
if { ! $fmri(inmelodic) } {
if { $fmri(level) == 1 } {
for { set session 1 } { $session <= $fmri(mult..."
(file "/usr/local/fsl/6.0.4/fsl/bin/feat" line 390)
I could not get what I was trying to achieve to work, so instead I looped through the same as above, copied my design.fsf file into each directory, and edited them with sed to have the right path.
#! /bin/sh
path=~/rj82/james_folder/data_copy/
cd $path
# Loop through the MND and Control directories
for directory in * ; do
cd $path
cd $directory
# Loop through each subject in each directory
for subject in ??? ; do
cd $path/$directory/$subject
# Loop through each scan for each subject
for scan in MR?? ; do
cd $path/$directory/$subject/$scan
# Copy template design.fsf into each scan folder
cp $path/../design.fsf design.fsf
# Change design.fsf file and replace the directory used
# to create the template with the current directory
current=$directory/$subject/$scan
sed -i "s|mnd/002/MR02|$current|g" design.fsf
# Run feat using custome design.fsf file
feat design.fsf
cd ..
done
done
done
My script navigates into each individual subject and scan, so I have change the design file to: set feat_files(1) "/fmri/fmri_data"
That was a good idea, but in order to make relative paths you have to also remove the leading /; thereto also hints the error message cannot create directory ‘/fmri’.
With truly relative paths in the .fsf file, it should also be possible to use a common design.fsf by calling feat /path/design.fsf

Bash - nested while loop: write txt file lines to csv

My bash script retrieves a list of folders, and stores them under a projectlist parameter.
Later, I want to loop through each folder, using the project name as a variable in the file path. Each project folder contains a ProjectFile.txt file, from which I want to append the contents to a common .csv file.
projectlist=$(./some/project/filepath/projects)
echo ${projectlist[#]}
while read project;
do
while read line;
do
echo "$project" "$line" >> /data/allData.csv
done < ./some/project/filepath/projects/"$project"/ProjectFile.txt
done < $projectlist
What it currently ends up doing however, is printing each individual element in the projectlist, without writing anything to the .csv file. Any idea where the issue lies?

Naming a file with a variable in a shell script

I'm writing a unix shell script that sorts data in ten subdirectories (labelled 1-10) of the home directory. In each subdirectory, the script needs to rename the files hehd.output and fort.hehd.time, as well as copy the file hehd.data to a .data file with a new name.
What I'd like it to do is rename each of these files in the following format:
AA.BB.CC
Where
AA = a variable in the hehd.data file within the subdirectory containing the file
BB = the name of the subdirectory containing the file (1-10)
CC = the original file name
Each subdirectory contains an hehd.data file, and each hehd.data file contains the string ij0=AA, where AA represents the variable I want to use to rename the files in the same subdirectory.
For example: When run, the script should search /home/4/hehd.data for the string ij0=2, then move /home/4/hehd.output to /home/4/2.4.hehd.output.
I'm currently using the grep command to have the script search for the string ij0=* and copy it to a new text file within the subdirectory. Next, the string ij0= is deleted from the text file, and then its contents are used to rename all target files in the same subdirectory. The last line of the shell script deletes the text file.
I'm looking for a better way to accomplish this, preferably such that all ten subdirectories can be sorted at once by the same script. My script seems incredibly inefficient, and doesn't do everything that I want it to by itself.
How can I improve this?
Any advice or suggestions would be appreciated; I'm trying to become a better computer user and that means learning better ways of doing things.
Try this:
fromdir=/home
for i in {1..10};do
AA=$(sed 's/ij0=\([0-9]*\)/\1/' "$fromdir/$i/hehd.data")
BB="$i"
for f in "$fromdir/$i/"*;do
CC="${f##*/}"
if [[ "$CC" = "hehd.data" ]]; then
echo cp "$f" "$fromdir/$i/$AA.$BB.$CC"
else
echo mv "$f" "$fromdir/$i/$AA.$BB.$CC"
fi
done
done
It loops over directories using Bash sequence {1..10].
In each directory, with the sed command the ij0 value is assigned to AA variable, the directory name is assigned to BB.
In the file loop, if the file is hehd.data it's copied, else it's renamed with the new name.
You can remove the echo before cp and mv commands if the output meets your needs.

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

Resources