Bash - File content - bash

I am trying to store content of multiple files to variable, but can't find the proper way. Example follows - I need to rewrite last line somehow as $content is not a file.
content="";
if (($# > 0)); then #input files given as arguments
for i ; do
if [ -r "${i}" ]; then
cat "${i}" >> "$content" #I need to append contents to existing variable

No need for a loop; the purpose of cat is to catenate multiple files.
content=$(cat "$#")
If you do want a loop for some reason, the conditional is superfluous; a loop over zero elements will execute zero times.
content=$(for i; do
test -r "$i" && cat "$i"
done)
In Bash, you can append to a variable with +=; more portably, you can do var="${var}newvalue".

Related

bash if and then statements both run

I'm running this in bash and even though there is a .txt file it prints out "no new folders to create" in the terminal.
Am I missing something?
FILES=cluster-02/*/*
for f in $FILES
do
if [[ $f == *."txt" ]]
then
cat $f | xargs mkdir -p
else
echo "No new folders to create"
fi
done;
As mentioned in the first comment, the behaviour is indeed as you might expect from your script: you run through all files, text files and other ones. In case your file is a text file, you perform the if-case and in case your file is another type of file, you perform the else-case.
In order to solve this, you might decide not to take the other files into account (only handle text files), I think you might do this as follows:
FILES=cluster-02/*/*.txt
You're looping over multiple files, so the first result may trigger the if and the second can show the else.
You could save the wildcard result in an array, check if there's something in it, and loop if so:
shopt -s nullglob
FILES=( foo/* )
if (( ${#FILES[#]} )); then
for f in "${FILES[#]}"; do
if [[ $f == *."txt" ]]; then
echo $f
fi
done
else
echo "No new folders to create"
fi
#!/usr/bin/env bash
# Create an array containing a list of files
# This is safer to avoid issues with files having special characters such
# as spaces, glob-characters, or other characters that might be cumbersome
# Note: if no files are found, the array contains a single element with the
# string "cluster-02/*/*"
file_list=( cluster-02/*/* )
# loop over the content of the file list
# ensure to quote the list to avoid the same pitfalls as above
for _file in "${file_list[#]}"
do
[ "${_file%.txt}" == "${_file}" ] && continue # skip, not a txt
[ -f "${_file}" ] || continue # check if the file exists
[ -r "${_file}" ] || continue # check if the file is readable
[ -s "${_file}" ] || continue # check if the file is empty
< "${_file}" xargs mkdir -p -- # add -- to avoid issues with entries starting with -
_c=1
done;
[ "${_c}" ] || echo "No new folders to create"

How to check filetype in if statement bash using wildecard and -f

subjects_list=$(ls -l /Volumes/Backup_Plus/PPMI_10 | awk '{ print $NF }')
filepath="/Volumes/Backup_Plus/PPMI_10/$subjects/*/*/S*/"
for subjects in $subjects_list; do
if [[ -f "${filepath}/*.bval" && -f "${filepath}/*.bvec" && -f "${filepath}/*.json" && -f "${filepath}/*.nii.gz" ]]; then
echo "${subjects}" >> /Volumes/Backup_Plus/PPMI_10/keep_subjects.txt
else
echo "${subjects}" >> /Volumes/Backup_Plus/PPMI_10/not_keep_subjects.txt
fi
done
problem is supposedly in the if statement, I tried this...
bvalfile = (*.bval)
bvecfile =(*.bvec)
jsonfile =(*.json)
niigzfile =(*.nii.gz)
if [[ -f "$bvalfile" && -f "$bvecfile" && -f "$jsonfile" && -f "$niigzfile" ]]; then
however that didn't work. Any help with syntax or errors or does it need to be changed completely. Trying to separate the files that have .^file types from those that don't by making two lists.
thanks
You're assigning filepath outside the for-subject loop but using the unset variable $subjects in it. You want to move that inside the loop.
Double-quoted wildcards aren't expanded, so both $filepath and your -f test will be looking for filenames with literal asterisks in them.
-f only works on a single file, so even if you fix the quotes, you'll have a syntax error if there's more than one file matching the pattern.
So I think what you want is something like this:
# note: array assignment -
# shell does the wildcard expansion, no ls required
prefix_list=( /Volumes/Backup_Plus/PPMI_10/* )
# and array expansion
for prefix in "${prefix_list[#]}"; do
# the subject is just the last component of the path
subject=${prefix##*/}
# start by assuming we're keeping this one
decision=keep
# in case filepath pattern matches more than one directory, loop over them
for filepath in "$prefix"/*/*/S*/; do
# if any of the files don't exist, switch to not keeping it
for file in "$filepath"/{*.bval,*.bvec,*.json,*.nii.gz}; do
if [[ ! -f "$file" ]]; then
decision=not_keep
# we have our answer and can stop looping now
break 2
fi
done
done
# now append to the correct list
printf '%s\n' "$subject" >>"/Volumes/Backup_Plus/PPMI_10/${decision}_subjects.txt"
done

Why doesn't counting files with "for file in $0/*; let i=$i+1; done" work?

I'm new in ShellScripting and have the following script that i created based on a simpler one, i want to pass it an argument with the path to count files. Cannot find my logical mistake to make it work right, the output is always "1"
#!/bin/bash
i=0
for file in $0/*
do
let i=$i+1
done
echo $i
To execute the code i use
sh scriptname.sh /path/to/folder/to/count/files
$0 is the name with which your script was invoked (roughly, subject to several exceptions that aren't pertinent here). The first argument is $1, and so it's $1 that you want to use in your glob expression.
#!/bin/bash
i=0
for file in "$1"/*; do
i=$(( i + 1 )) ## $(( )) is POSIX-compliant arithmetic syntax; let is deprecated.
done
echo "$i"
That said, you can get this number more directly:
#!/bin/bash
shopt -s nullglob # allow globs to expand to an empty list
files=( "$1"/* ) # put list of files into an array
echo "${#files[#]}" # count the number of items in the array
...or even:
#!/bin/sh
set -- "$1"/* # override $# with the list of files matching the glob
if [ -e "$1" ] || [ -L "$1" ]; then # if $1 exists, then it had matches
echo "$#" # ...so emit their number.
else
echo 0 # otherwise, our result is 0.
fi
If you want to count the number of files in a directory, you can run something like this:
ls /path/to/folder/to/count/files | wc -l

shell script : write entire contents of for loop to file, not only last line

I have an array in a shell script with index and value . I am able to print this array. How do i write the entire array into a text file?
What I'm currently doing is this:
for i in "${!array[#]}"; do
printf "%s\t%s\n" "$i" "${array[$i]}" >outfile
done
However, only the last element of the array is present in outfile. How can I fix this?
The easy answer is to put the redirection after the done, not on the printf:
for key in "${!array[#]}"; do
value=${array[$key]}
[[ $key = *[$'\t\n']* ]] && continue # security: disallow keys with tabs or newlines
[[ $value = *[$'\n']* ]] && continue # security: disallow values with newlines
printf "%s\t%s\n" "$i" "${array[$i]}"
done >outfile
Note those checks -- I'm not doing them again later in this code, but they're necessary to allow a value in your format to inject other key/value pairs, or to allow "values" to actually specify other keys.
That said, to replace an entire file at once, one should use a write-and-rename pattern to ensure atomicity. The general pattern is:
tempfile=$(mktemp outfile.XXXXXX)
write_your_data >"$tempfile"
mv -- "$tempfile" outfile
write_your_data need not be a placeholder -- it could also be a function encapsulating the loop itself:
write_your_data() {
local i
for i in "${!array[#]}"; do
printf "%s\t%s\n" "$i" "${array[$i]}"
done
}
Just to be clear, the reason you are only seeing the last line is because you are using > instead of >>.
> redirects the output to a file, either creating it or overwriting what was already there.
>> also redirects the output, but appends to a file...adding it to the end of it.
Every time your for loop iterated, you were "recreating" the file.
for i in "${!array[#]}"; do
printf "%s\t%s\n" "$i" "${array[$i]}" >> outfile
done

Replace text between 2 strings in bash with result of ls

I want to create an automation for a deployment, the js/css are generated with a prefix and I want to import them in the php file between the tags
Expected Output
...
/*bashStart*/
drupal_add_css(drupal_get_path("module","myModule")."/styles/c91c6d11.main.css");
drupal_add_js(drupal_get_path("module","myModule")."/scripts/j91c6d11.main.js");
/*bashEnd*/
...
I used awk and got me here so far but I have a problem, it's generating
...
/*bashStart*/
drupal_add_css(drupal_get_path("module","myModule")."/styles/c91c6d11.main.css
");
drupal_add_js(drupal_get_path("module","myModule")."/scripts/j91c6d11.main.js
");
/*bashEnd*/
...
here is the awk script:
awk 'BEGIN {p=1}/Start/{print;printf("drupal_add_css(drupal_get_path(\"module\",\"myModule\").\"/styles/");system("ls styles");printf("\");\n");printf("drupal_add_js(drupal_get_path(\"module\",\"myModule\").\"/scripts/");system("ls scripts");printf("\");\n");p=0}/Finish/{p=1} p' myModule.module > tmp;
Using ls within awk isn't very nice - I think you can do this entirely in the shell:
#!/bin/bash
p=1
while read -r line; do
[[ $line = '/*bashEnd*/' ]] && p=1
(( p )) && echo "$line"
if [[ $line = '/*bashStart*/' ]]; then
p=0
for style in styles/*; do
echo 'drupal_add_css(drupal_get_path("module","myModule")."/styles/'"$style"'");'
done
for script in scripts/*; do
echo 'drupal_add_js(drupal_get_path("module","myModule")."/scripts/'"$script"'");'
done
fi
done < file.php > output.php
Loop through the input file until you reach the "bashStart" line, then add the lines you want. Output goes to a file output.php which you can check before overwriting the original file. If you're feeling confident you can add && mv output.php file.php to the done line, to overwrite the original file.
The flag p controls which lines are printed. It is set to 0 when the "bashStart" line is reached and back to 1 when the bashEnd line is reached, so lines between the two are replaced.

Resources