Passed variable is incorrect - bash

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.

Related

How to change content in variable based on pwd in 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.

How to create a list of sequentially numbered folders using an existing folder name as the base name

I've done a small amount of bash scripting. Mostly modifying a script to my needs.
On this one I am stumped.
I need a script that will read a sub-folder name inside a folder and make a numbered list of folders based on that sub-folder name.
Example:
I make a folder named “Pictures”.
Then inside I make a sub-folder named “picture-set”
I want a script to see the existing sub-folder name (picture-set) and make 10 more folders with sequential numbers appended to the end of the folder names.
ex:
folder is: Pictures
sub-folder is: picture-set
want to create:
“picture-set-01”
“picture-set-02”
“picture-set-03”
and so forth up to 10. Or a number specified in the script.
The folder structure would look like this:
/home/Pictures/picture-set
/home/Pictures/picture-set-01
/home/Pictures/picture-set-02
/home/Pictures/picture-set-03
... and so on
I am unable to tell the script how to find the base folder name to make additional folders.
ie: “picture-set”
or a better option:
Would be to create a folder and then create a set of numbered sub-folders based on the parent folder name.
ex:
/home/Songs - would become:
/home/Songs/Songs-001
/home/Songs/Songs-002
/home/Songs/Songs-003
and so on.
Please pardon my bad formatting... this is my first time asking a question on a forum such as this. Any links or pointers as to proper formatting is welcome.
Thanks for the help.
Bash has a parameter expansion you can use to generate folder names as arguments to the mkdir command:
#!/usr/bin/env bash
# Creates all directories up to 10
mkdir -p -- /home/Songs/Songs-{001..010}
This method is not very flexible if you need to dinamically change the range of numbers to generate using variables.
So you may use a Bash for loop and print format the names with desired number of digits and create each directory in the loop:
#!/usr/bin/env bash
start_index=1
end_index=10
for ((i=start_index; i<=end_index; i++)); do
# format a dirpath with the 3-digits index
printf -v dirpath '/home/Songs/Songs-%03d' $i
mkdir -p -- "$dirpath"
done
# Prerequisite:
mkdir Pictures
cd Pictures
# Your script:
min=1
max=12
name="$(basename "$(realpath .)")"
for num in $(seq -w $min $max); do mkdir "$name-$num"; done
# Result
ls
Pictures-01 Pictures-03 Pictures-05 Pictures-07 Pictures-09 Pictures-11
Pictures-02 Pictures-04 Pictures-06 Pictures-08 Pictures-10 Pictures-12

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

Rename subfolder: add leading word from parent folder

I have a large music library organized in Artist folders with Album subfolders:
Artist 1
- Album A
-- File 1
-- File 2
- Album B
-- File 1
-- File 2
Artist 2
- Album C
-- File 1
-- File 2
- Album D
-- File 1
-- File 2
Now I want to rename all Album folders by adding the Artist name as prefix AND move all Album folders in to the root directory, like this:
Artist 1 - Album A
- File 1
- File 2
Artist 1 - Album B
- File 1
- File 2
Artist 2 - Album C
- File 1
- File 2
Artist 2 - Album D
- File 1
- File 2
What is the best way to do that? I tried to do it with Total Commander, but I don't get it. Or maybe shell, mp3tag? I use Windows 10.
Thanks!
This could be done in several ways, actually. I'll show how in bash, but that's only if you have that thing of Bash on Ubuntu on Windows. Again:
BASH
Assuming that your music library is ~/Music/, that all the artist folders are inside, that you accept to have everything moved to a new folder, assuming that it doesn't yet exist (because if you had an artist folder named 'CoolSinger' and just accidentally you also have another artist folder named 'CoolSinger - CoolAlbum', by a mistake, there would be problems...), and that there are not any other files involved than what you state, the shell script would look like this:
#!/bin/bash
mkdir ~/NewMusic/
for artistdir in ~/Music/*; do
artistname="$(basename "${artistdir}")"
for albumdir in "${artistdir}"/*; do
albumname="$(basename "${albumdir}")"
mv "${albumdir}" ~/NewMusic/"${artistname} - ${albumname}" 2>/dev/null
done
done
And let me explain it in human:
Create the destination folder. Then, for every artist (assuming all are folders); iterate through every album sub-folder, moving it to the new folder, and renaming it, by the way, using the artist name and folder name from the actual folder names. If everything went OK, this would result in:
A folder ~/Music/ with the original sub-folder names, but they're now empty.
A folder ~/NewMusic with the desired structure.
If an artist folder is empty, the shell would try to throw an error because it expands to the literal string (explained later), but don't worry, because if it is empty, then anything is to do with it, so you can ignore it, appending 2>/dev/null to the command. However, if anything else goes wrong with this, it will also be silenced.
If you wanted to, let's say, stay with the original files, and have a copy of all that with the desired structure, you should change the mv command for a cp -R one.
And all that is for a script, in a file. If you wanted to execute it right from the command line, which would make somewhat tricky to change the folder name for the music library, it would look like this:
mkdir ~/NewMusic/; for artistdir in ~/Music/*; do artistname="$(basename "${artistdir}")"; for albumdir in "${artistdir}"/*; do albumname="$(basename "${albumdir}")"; mv "${albumdir}" ~/NewMusic/"${artistname} - ${albumname}"; done; done
And an explanation of everything there:
mkdir creates the directory.
for A in B ; do C ; done is a loop. It loops through every string separated by one of the IFS characters in B, and assign it as a value for the variable A, while it executes C.
Note that the text in the script has an asterisk. The shell expands a string with an asterisk to what it finds to match the pattern. For instance, if you had a bunch of Word documents, typing echo *.docx would expand to every name matching that ending, in the current folder (in another case, echo /path/*.docx).
So, for every folder inside the specified directory, assign (once for every artist folder) the variable artistname with the base name of the file specified (in this case, the folder) as its value. So, basename /path/to/anything returns anything.
With the help of another for loop, do for every element inside the artist directory, rename it as specified, and if the new name is in another directory, it will move it there.
Again, the 2>/dev/null redirection is there to redirect all output in stderr to the void. Output in stdout would be still printed, though, if there was any.
SOME IMPORTANT NOTES
I don't have any idea whether the shell from Bash on Ubuntu on Windows detects /dev/null as the special character file that a standard UNIX machine has or not. Likely it does, or it wouldn't be compatible with so many things.
This is only if you have Bash on Ubuntu on Windows in your Windows 10. If I recall correctly, the chance of using it was implemented with the Anniversary Update. If you have the update, but not bash, I recommend you to install it. There are several guides over that on the Internet.
About the folder structure. I suspect the same about /dev/null as for the POSIX folder structure: that it follows it, as it's not just Bash on Windows, but Bash on Ubuntu on Windows.
I found an answer by myself. The best way to do it is to use mp3tag tool. You can create new folders and copy your files in it.
Select Converter > Tag - Filename
Type in:
E:\%artist% - %album%\$num(%track%,2) - %title%
You'll receive a directory with Artist - Albumname\01 - File.mp3

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.

Resources