I am writing a shell script to read all the files in the give directory by the user input then count how many files with that extension. I just started learning Bash and I am not sure why it this not locating the files or reading the directory. I am only putting 2 example but my count is always 0.
This is how I run my script
$./check_ext.sh /home/user/temp
my script check_ext.sh
#!/bin/bash
count1=0
count2=0
for file in "ls $1"
do
if [[ $file == *.sh ]]; then
echo "is a txt file"
(( count1++ ))
elif [[ $file == *.mp3 ]]; then
echo "is a mp3 file"
(( count2++ ))
fi
done;
echo $count $count2
"ls $1" doesn't execute ls on $1, it just a plain string. Command substitution syntax is $(ls "$1")
However there is no need to use ls, just use globbing:
count1=0
count2=0
for file in "$1"/*; do
if [[ $file == *.sh ]]; then
echo "is a txt file"
(( count1++ ))
elif [[ $file == *.mp3 ]]; then
echo "is a mp3 file"
(( count2++ ))
fi
done
echo "counts: $count1 $count2"
for file in "$1"/* will iterate through all the files/directories in the directory denoted by $1
EDIT: For doing it recursively inside a directory:
count1=0
count2=0
while IFS= read -r -d '' file; do
if [[ $file == *.sh ]]; then
echo "is a txt file"
(( count1++ ))
elif [[ $file == *.mp3 ]]; then
echo "is a mp3 file"
(( count2++ ))
fi
done < <(find "$1" -type f -print0)
echo "counts: $count1 $count2"
POSIXly:
count1=0
count2=0
for f in "$1"/*; do
case $f in
(*.sh) printf '%s is a txt file\n' "$f"; : "$((count1+=1))" ;;
(*.mp3) printf '%s is a mp3 file\n' "$f"; : "$((count2+=1))" ;;
esac
done
printf 'counts: %d %d\n' "$count1" "$count2"
You can use Bash arrays for this too: if you only want to deal with extensions sh and mp3:
#!/bin/bash
shopt -s nullglob
shs=( "$1"/*.sh )
mp3s=( "$1"/*.mp3 )
printf 'counts: %d %d\n' "${#shs[#]}" "${#mp3s[#]}"
If you want to deal with more extensions, you can generalize this process:
#!/bin/bash
shopt -s nullglob
exts=( .sh .mp3 .gz .txt )
counts=()
for ext in "${exts[#]}"; do
files=( "$1"/*."$ext" )
counts+=( "${#files[#]}" )
done
printf 'counts:'
printf ' %d' "${counts[#]}"
echo
If you want to deal with all extensions (using associative arrays, available in Bash ≥4)
#!/bin/bash
shopt -s nullglob
declare -A exts
for file in "$1"/*.*; do
ext=${file##*.}
((++'exts[$ext]'))
done
for ext in "${!exts[#]}"; do
printf '%s: %d\n' "$ext" "${exts[$ext]}"
done
Related
I searched and couldn't find anything, maybe I can't understand the problem properly.
I have a bash function who read files in current dir and sub dir's, I'm trying to arrange the text and analyze the data but somehow I'm losing lines if I'm using pipeline.
the code:
function recursiveFindReq {
for file in *.request; do
if [[ -f "$file" ]]; then
echo handling "$file"
echo ---------------with pipe-----------------------
cat "$file" | while read -a line; do
if (( ${#line} > 1 )); then
echo ${line[*]}
fi
done
echo ----------------without pipe----------------------
cat "$file"
echo
echo num of lines: `cat "$file" | wc -l`
echo --------------------------------------
fi
done
for dir in ./*; do
if [[ -d "$dir" ]]; then
echo cd to $dir
cd "$dir"
recursiveFindReq "$1"
cd ..
fi
done
}
the output is:
losing lines even when they meet requirements
I marked with 2 red arrows the place I'm losing info
I am trying to make a script that will prompt the user for two file names. verify the files names exist and are regular files. If both exist and are regular files display a message which file of the two files are older. If they're the same then just display either one.
Any help would be appericated
#!/bin/bash
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
You can use this for any number of files:
#!/usr/bin/env bash
(($# > 0)) || exit 1
files=("$#")
for file in "${files[#]}"; do
[[ -f $file ]] || { echo "'$file' is not a regular file!"; exit 2; }
done
oldest=${files[0]}
for file in "${files[#]}"; do
[[ $file -ot $oldest ]] && oldest=$file
done
echo "Oldest is: $oldest"
You can use the -nt and -ot flags to compare two files by modification date. Info from the test manpage below (man test)
FILE1 -nt FILE2
FILE1 is newer (modification date) than FILE2
FILE1 -ot FILE2
FILE1 is older than FILE2
Try this:
#!/bin/bash
FILE1=$1
FILE2=$2
for f in "$FILE1" "$FILE2" ; do
if [ ! -f "$f" ] ; then
echo "File $f does not exist or is not a regular file"
exit 1
fi
done
if [ "$FILE1" -nt "$FILE2" ] ; then
echo "$FILE1"
else
echo "$FILE2"
fi
Sorry for asking this question again. I have already received answer but with using find but unfortunately I need to write it without using any predefined commands.
I am trying to write a script that will loop recursively through the subdirectories in the current directory. It should check the file count in each directory. If file count is greater than 10 it should write all names of these file in file named "BigList" otherwise it should write in file "ShortList". This should look like:
---<directory name>
<filename>
<filename>
<filename>
<filename>
....
---<directory name>
<filename>
<filename>
<filename>
<filename>
....
My script only works if subdirectories don't include subdirectories in turn.
I am confused about this because it doesn't work as I expect.
Here is my script
#!/bin/bash
parent_dir=""
if [ -d "$1" ]; then
path=$1;
else
path=$(pwd)
fi
parent_dir=$path
loop_folder_recurse() {
local files_list=""
local cnt=0
for i in "$1"/*;do
if [ -d "$i" ];then
echo "dir: $i"
parent_dir=$i
echo before recursion
loop_folder_recurse "$i"
echo after recursion
if [ $cnt -ge 10 ]; then
echo -e "---"$parent_dir >> BigList
echo -e $file_list >> BigList
else
echo -e "---"$parent_dir >> ShortList
echo -e $file_list >> ShortList
fi
elif [ -f "$i" ]; then
echo file $i
if [ $cur_fol != $main_pwd ]; then
file_list+=$i'\n'
cnt=$((cnt + 1))
fi
fi
done
}
echo "Base path: $path"
loop_folder_recurse $path
How can I modify my script to produce the desired output?
This bash script produces the output that you want:
#!/bin/bash
bigfile="$PWD/BigList"
shortfile="$PWD/ShortList"
shopt -s nullglob
loop_folder_recurse() {
(
[[ -n "$1" ]] && cd "$1"
for i in */; do
[[ -d "$i" ]] && loop_folder_recurse "$i"
count=0
files=''
for j in *; do
if [[ -f "$j" ]]; then
files+="$j"$'\n'
((++count))
fi
done
if ((count > 10)); then
outfile="$bigfile"
else
outfile="$shortfile"
fi
echo "$i" >> "$outfile"
echo "$files" >> "$outfile"
done
)
}
loop_folder_recurse
Explanation
shopt -s nullglob is used so that when a directory is empty, the loop will not run. The body of the function is within ( ) so that it runs within a subshell. This is for convenience, as it means that the function returns to the previous directory when the subshell exits.
Hopefully the rest of the script is fairly self-explanatory but if not, please let me know and I will be happy to provide additional explanation.
i need to manage my server's maildir messages.
i need to know how i can efficiently rename individual message files as already [R]ead, or vice versa, preferably using standard bash.
1408429273.V825I1cce0a1M366263.server0:2,S
1408500713.V825I1ccddaaM341812.server0:2,S
1408502356.V825I1ccddf3M195155.server0:2,S
1408502501.V825I1cce048M253486.server0:2,RS
if a file is already marked as [R]ead, i don't want to trip over it again, or worse, rename it wrongly.
Try:
#!/bin/bash
shopt -s nullglob
for F in /path/to/files/*:*,*; do
CODE=${F##*,}
[[ $CODE != *R* ]] && echo mv "$F" "${F%,*},R${CODE}"
done
Remove echo when confirmed that it's working.
More strict:
#!/bin/bash
shopt -s nullglob
shopt -s extglob
for F in /path/to/files/*:+([0-9]),*([A-Z]); do
CODE=${F##*,}
[[ $CODE != *R* ]] && echo mv "$F" "${F%,*},R${CODE}"
done
To rename as not read:
#!/bin/bash
shopt -s nullglob
shopt -s extglob
for F in /path/to/files/*:+([0-9]),*([A-Z]); do
CODE=${F##*,}
[[ $CODE == *R* ]] && echo mv "$F" "${F%,*},${CODE//R}"
done
Script:
#!/bin/bash
shopt -s extglob
CHANGE_MODE=''
FILES=()
function show_usage_and_exit {
echo "Usage: $0 option [--] file [file2 ...]" >&2
echo "Options:" >&2
echo " -r, --read Add read flag to filenames." >&2
echo " -u, --unread Remove read flag from filenames." >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
-r|--read)
CHANGE_MODE='R'
shift
;;
-u|--unread)
CHANGE_MODE='U'
shift
;;
-h|--help)
show_usage_and_exit
;;
--)
shift
FILES+=("$#")
break
;;
-*)
echo "Invalid option: $1" >&2
show_usage_and_exit
;;
*)
FILES+=("$1")
;;
esac
shift
done
if [[ -z $CHANGE_MODE ]]; then
echo "No mode specified."
show_usage_and_exit
elif [[ ${#FILES[#]} -eq 0 ]]; then
echo "No file was specified."
show_usage_and_exit
else
for F in "${FILES[#]}"; do
if [[ ! -f $F ]]; then
echo "File does not exist or is not a file: $F" >&2
exit 1
elif [[ $F != *:+([0-9]),*([A-Z]) ]]; then
echo "File format is not recognized: $F" >&2
exit 1
fi
done
fi
if [[ $CHANGE_MODE == R ]]; then
for F in "${FILES[#]}"; do
CODE=${F##*,}
[[ $CODE != *R* ]] && echo mv "$F" "${F%,*},R${CODE}"
done
else
for F in "${FILES[#]}"; do
CODE=${F##*,}
[[ $CODE == *R* ]] && echo mv "$F" "${F%,*},${CODE//R}"
done
fi
The directories are variables set to the full-path
for e in "$DIR_0" "$DIR_1" "$DIR_2"
do
for i in $e/*
do
echo $i
done
The output for each line is the full path. I want only the name of each file
You are looking for basename.
This is the Bash equivalent of basename:
echo "${i##*/}"
It strips off everything before and including the last slash.
If you truly do not wish to recurse you can achieve that more succinctly with this find command:
find "$DIR_0" "$DIR_1" "$DIR_2" -type f -maxdepth 1 -exec basename{} \;
If you wish to recurse over subdirs simply leave out maxdepth:
find "$DIR_0" "$DIR_1" "$DIR_2" -type f -exec basename{} \;
to traveling a directory recursively with bash
try this you can find it here
#! /bin/bash
indent_print()
{
for((i=0; i < $1; i++)); do
echo -ne "\t"
done
echo "$2"
}
walk_tree()
{
local oldifs bn lev pr pmat
if [[ $# -lt 3 ]]; then
if [[ $# -lt 2 ]]; then
pmat=".*"
else
pmat="$2"
fi
walk_tree "$1" "$pmat" 0
return
fi
lev=$3
[ -d "$1" ] || return
oldifs=$IFS
IFS=""
for el in $1/ *; do
bn=$(basename "$el")
if [[ -d "$el" ]]; then
indent_print $lev "$bn/"
pr=$( walk_tree "$el" "$2" $(( lev + 1)) )
echo "$pr"
else
if [[ "$bn" =~ $2 ]]; then
indent_print $lev "$bn"
fi
fi
done
IFS=$oldifs
}
walk_tree "$1" "\.sh$"
See also the POSIX compliant Bash functions to replace basename & dirname here:
http://cfaj.freeshell.org/src/scripts/