The user will give any amount of positional parameters that they want (which are all C programs). I want to make it so that all of the C programs compile. However this is not working; does anyone have a solution?
echo '#!/bin/bash' >> compile
echo if [ "-o"='$1' ] >> compile
echo then >> compile
echo for (i=3; i<='$#'; i++) >> compile
echo do >> compile
echo gcc -o '$2' '${i}' >> compile
echo fi >> compile
Don't use a bunch of echo statements, use a here-doc. Putting quotes around the token after << prevents expanding variables inside the here-doc.
cat <<'EOF' >>compile
'#!/bin/bash'
if [ "-o" = "$1" ]
then
for ((i=3; i <= $#; i++))
do
gcc -o "$2" "${!i}"
done
fi
EOF
Otherwise, you need to escape or quote all the special characters -- you were getting an error because you didn't escape the < in the for() line.
Other mistakes: you need spaces around = in the [ command, and you were missing done at the end of the for loop. And to access a variable indirectly, you need to use ${!var} syntax.
The usual way to iterate over all the arguments is with a simple:
for arg
loop. When there's no in after for variable, it loops over the arguments. You just need to remove the -o outputfile argument first:
output=$2
shift 2 # remove first 2 arguments
for arg
do
gcc -o "$output" "$arg"
done
Here is how I would edit what you originally posted:
$ cat test.sh
echo -e "#!/bin/bash" > compile.sh
echo -e "if [ \"\${1}\" == \"-o\" ]; then" >> compile.sh
echo -e "\tlist_of_arguments=\${#:3} #puts all arguments starting with \$3 into one argument" >> compile.sh
echo -e "\tfor i in \${list_of_arguments}; do" >> compile.sh
echo -e "\t\techo \"gcc \${1} '\${2}' '\${i}'\"" >> compile.sh
echo -e "\tdone" >> compile.sh
echo -e "fi" >> compile.sh
$ ./test.sh
$ cat compile.sh
#!/bin/bash
if [ "${1}" == "-o" ]; then
list_of_arguments=${#:3} #puts all arguments starting with $3 into one argument
for i in ${list_of_arguments}; do
echo "gcc ${1} '${2}' '${i}'"
done
fi
$ chmod +x compile.sh
$ ./compile.sh -o one two three four five
gcc -o 'one' 'two'
gcc -o 'one' 'three'
gcc -o 'one' 'four'
gcc -o 'one' 'five'
for demonstration purposed, I echoed the gcc command in test.sh. To actually run gcc instead of echoing it, change line five in test.sh from:
echo -e "\t\techo \"gcc \${1} '\${2}' '\${i}'\"" >> compile.sh
to
echo -e "\t\tgcc \${1} '\${2}' '\${i}'" >> compile.sh
or pipe the echo to sh like so:
echo -e "\t\techo \"gcc \${1} '\${2}' '\${i}'\" \| sh" >> compile.sh
Related
Startup action:
I am trying to make a function that I will call with .bash_functions which is sourced in my .bashrc file.
Goal:
I am trying to reduce the amount of code required. If I make a function with repeated commands that have (mostly minor) differences for each command line I end up with a HUGE function...
Purpose of function:
The function will search the current working directory for files that match an array of predefined extensions. If a file with a matching extension is found it will execute a certain customized command line. If another match is found with a different extension then another type of command must be used instead of the first command that was mentioned and so forth.
If the following files are in the current working directory:
file1.7z
file2.bz2
file3.gz
file4.tgz
file5.xz
Then running the function in a terminal will output the following lines:
7z x -o/root/file1 /root/file1.7z
tar -xvfj /root/file2.bz2 -C /root/file2
tar -xvf /root/file3.gz -C /root/file3
tar -xvf /root/file4.tgz -C /root/file4
tar -xvf /root/file5.xz -C /root/file5
Where I am at so far:
I don't yet have the array set up because I am stuck figuring out the flow to loop the commands correctly. I have the below script which does what I want however like I mentioned I want to slim it down if possible (learn some new tricks from you guys)!
untar()
{
clear
for i in *.*
do
local EXT="$(echo "${i}" | sed 's/.*\.//')"
if [ -n "${EXT}" ]; then
if [[ "${EXT}" == '7z' ]]; then
if [ ! -d "${PWD}"/"${i%%.*}" ]; then mkdir -p "${PWD}"/"${i%%.*}"; fi
echo 7z x -o"${PWD}"/"${i%%.*}" "${PWD}"/"${i}"
elif [[ "${EXT}" == 'bz2' ]]; then
if [ ! -d "${PWD}"/"${i%%.*}" ]; then mkdir -p "${PWD}"/"${i%%.*}"; fi
echo tar -xvfj "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
elif [[ "${EXT}" == 'gz' ]]; then
if [ ! -d "${PWD}"/"${i%%.*}" ]; then mkdir -p "${PWD}"/"${i%%.*}"; fi
echo tar -xvf "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
elif [[ "${EXT}" == 'tgz' ]]; then
if [ ! -d "${PWD}"/"${i%%.*}" ]; then mkdir -p "${PWD}"/"${i%%.*}"; fi
echo tar -xvf "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
elif [[ "${EXT}" == 'xz' ]]; then
if [ ! -d "${PWD}"/"${i%%.*}" ]; then mkdir -p "${PWD}"/"${i%%.*}"; fi
echo tar -xvf "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
fi
fi
done;
}
Required to function:
Essentially, two different tar commands and a single 7z command are needed (as far as i can come up with anyways)
Command 1: tar -xvf <target.{gz,tgz,xz}> -C <output folder>
Command 2: tar -xvfj <target.bz2> -C <output folder>
Command 3: 7z x -o<output folder> <target.7z>
Any ideas you can throw my way?
Since you're familiar with parameter substitution I'd start with eliminating the overhead of the dual subprocess invocations for finding EXT; shorter code but also a performance improvement as this doesn't require spawning/cleaning-up any subprocesses (for each pass through the loop):
local EXT="${i##*.}"
NOTE: this assumes all files have a single extension (eg, .tgz instead of tar.gz) otherwise OP may need to add more logic to determine how to process files with multiple extensions (eg, abc.tar.gz vs a.b.c.tar.gz)
Next idea would be to pull the mkdir logic out by itself; this eliminates 4 mkdir lines:
if [ ! -d "${PWD}"/"${i%%.*}" ]; then mkdir -p "${PWD}"/"${i%%.*}"; fi
# or
[[ ! -d "${PWD}"/"${i%%.*}" ]] && mkdir -p "${PWD}"/"${i%%.*}"
Small detour ...
OP has mentioned wanting to use an array to manage a list of extensions and while such an approach is doable it would also require some thought on how to store and reference the associated commands.
Assuming this will be the only piece of code that needs to process a list of extensions I'd probably opt for 'hardcoding' the logic for each extension (as opposed to storing in a resource/config file and then loading into an array). Net result, stick with current approach but with a few improvements.
Back to code (re)design ...
Next idea would be to collapse the tar calls into a single call with a test for bz2; this eliminates 3 tests and 3 tar lines:
if [[ "${EXT}" == '7z' ]]; then
echo 7z x -o"${PWD}"/"${i%%.*}" "${PWD}"/"${i}"
else
jflag=""
[[ "${EXT}" == 'bz2' ]] && jflag="j"
echo tar -xvf${jflag} "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
fi
Personally, I'd probably opt for a case statement:
case "${EXT}" in
7z)
echo 7z x -o"${PWD}"/"${i%%.*}" "${PWD}"/"${i}"
;;
*)
jflag=""
[[ "${EXT}" == 'bz2' ]] && jflag="j"
echo tar -xvf${jflag} "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
;;
esac
Pulling this all together:
untar()
{
clear
local EXT
for i in *.*
do
EXT="${i##*.}"
[[ ! -d "${PWD}"/"${i%%.*}" ]] && mkdir -p "${PWD}"/"${i%%.*}"
case "${EXT}" in
7z)
echo 7z x -o"${PWD}"/"${i%%.*}" "${PWD}"/"${i}"
;;
*)
jflag=""
[[ "${EXT}" == 'bz2' ]] && jflag="j"
echo tar -xvf${jflag} "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
;;
esac
done
}
Posting this as another example for others to see that I came up with after seeing the great example by markp-fuso.
I added zip files to the list this time.
untar()
{
clear
local EXT
for i in *.*
do
EXT="${i##*.}"
[[ ! -d "${PWD}"/"${i%%.*}" ]] && mkdir -p "${PWD}"/"${i%%.*}"
case "${EXT}" in
7z|zip)
7z x -o"${PWD}"/"${i%%.*}" "${PWD}"/"${i}"
;;
bz2|gz|xz)
jflag=""
[[ "${EXT}" == 'bz2' ]] && jflag="j"
tar -xvf${jflag} "${PWD}"/"${i}" -C "${PWD}"/"${i%%.*}"
;;
esac
done
}
I don't know what I am doing and could use some assistance with my script.
$ ./mysql.sh LOCALIP 'SELECT LOCALIP FROM Host'
mysql.sh
#!/bin/bash
source $PWD/data/login
mapfile -t "$1" < <(mysql -N ""$DB"" -h""$HOST"" -u""$USER"" -p""$PASS"" -se "$2")
echo ${$1[0]}
echo ${$1[1]}
echo ${$1[2]}
echo ${$1[3]}
fi
Output
[シ]owner#gwpi ~/scriptdir $./mysql.sh LOCALIP 'SELECT LOCALIP FROM Host'
./mysql.sh: line 10: ${$1[0]}: bad substitution
Simply replace the varible $1 with var here.It works.
$ mapfile -t var < <(mysql -N ""$DB"" -h""$HOST"" -u""$USER"" -p""$PASS"" -se "$2")
$ echo" ${var[#]}"
Original script has two problems.
You cannot change $1 arg in this way.Right style refers to this
should be better using more meaningful variables than $1
I was trying to write a script which involved printing an array with a delimiter. Unfortunately, the printf command that worked typing into the command line did not work when run as a script. It also seems that bash and zsh handle things differently.
This gives the intended output, it works if I just paste directly into the terminal like this:
➜ ~ array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[#]}"
echo "$tmp"
a
b
c
d
However, when run as a script, the output was not what I wanted:
➜ ~ echo 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[#]}"
echo "$tmp"' > test1.sh
➜ ~ bash test1.sh
abcdn
Then I remembered that zsh is my default shell, so I tried running it explicitly with zsh. The results were closer to what I wanted (there was something in between each array element), but still not what I got when pasting into the terminal. Why is the \ in \n ignored in this case?
➜ ~ zsh test1.sh
anbncndn
Also, is there a handy table somewhere of the key differences in basic commands like echo and printf between shells and in scripts vs the terminal?
EDIT:
I noticed that my example was flawed in that when I created the script test.sh file, the echo left out the backslash before the n.
➜ ~ echo '
array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[#]}"
echo "$tmp"
'
array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp %sn "${array[#]}"
echo "$tmp"
But what threw me off was that zsh -c showed the same behavior. Is it also stripping out backslashes?
➜ ~ zsh -c 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[#]}"
echo "$tmp"'
anbncndn
Edit 2: It's that escape characters work in each of these situations.
three preceding backslashes turn in back into a good ol' newline.
zsh -c 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\\\\n' "${array[#]}"
echo "$tmp"'
a
b
c
d
EDIT3: So the escaping in a script works differently from '-c'
The script:
#!/bin/zsh
array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\\n' "${array[#]}"
echo "$tmp"
the result:
➜ ~ zsh test.sh
a
b
c
d
zsh handles arrays differently than bash. In zsh (your interactive shell), array+=b really does append the string b as a new array element.
In bash, however, the same command only appends the string b to the first element of the array; to append a new element, you need to use array+=(b).
zsh -c 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[#]}"
echo "$tmp"'
anbncndn
In this, the argument to -c is split into three parts:
'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '
%s\n
and
' "${array[#]}"
echo "$tmp"'
which are all concatenated together. The sections inside single quotes aren't subject to substitutions, expansions, special handling of backslashes, etc. The middle section is, and becomes %sn before it's passed to zsh for execution. When you add extra backslashes, %s\\\\n becomes %s\\n, and the double backslash is turned into a single one by the zsh executing the command.
In your script, because you're not trying to quote the entire thing to make it a single argument, printf -v tmp '%s\\n' "${array[#]}" is split and treated the way you want. However, because you're doubling up the backslash there, $tmp is set to a\nb\nc\nd\n. Then when you echo it, the default zsh behavior is to expand backslash escape sequences in the string being printed, so it looks right. This can be disabled with the BSD_ECHO shell option, or using echo -E. If you want actual newlines, use a single backslash inside single quotes, or a double one inside double quotes or unquoted.
Demonstration:
$ printf -v tmp '%s\\n' a b c d
$ echo "$tmp"
a
b
c
d
$ echo -E "$tmp"
a\nb\nc\nd\n
$ setopt BSD_ECHO
$ echo "$tmp"
a\nb\nc\nd\n
$ printf -v tmp '%s\n' a b c d
$ echo "$tmp"
a
b
c
d
Given:
a C file with several included header files
a bunch of include file search folder
Is there a way to generate some kind of include map for the C file?
Though IDEs can sometimes help locate the definition/declaration of a symbol in the header file. But I think an include map can give me a better insight into how these files are related when project gets complicated. And identify issues such as circular includes.
ADD 1
A similar thread but not much helpful.
It only generates an include hierarchy in text in the Output window when building.
And it only works for native VC++ project. Not work for NMAKE C project.
Displaying the #include hierarchy for a C++ file in Visual Studio
ADD 2
I just tried the Include Manager mentioned in above thread. Though not free, it's not expensive and perfectly fits in my scenario.
Not sure if this is quite what you're after, but I was curious what a graph of this would look like, so I threw this together. It's a bit of a mess, but workable for a throw-away script:
#!/bin/bash
INCLUDE_DIRS=()
# Add any include dirs here
# INCLUDE_DIRS+=("/usr/include")
# If you want to add flags for some pkg-config modules (space
# separated)
PKG_CONFIG_PKGS=""
FOLLOW_SYS_INCLUDES=y
while read dir; do
dir="$(readlink -f "${dir}")"
for d in "${INCLUDE_DIRS[#]}"; do
if [ "${dir}" = "${d}" ]; then
continue
fi
done
INCLUDE_DIRS+=("${dir}")
done < <(echo | cc -Wp,-v -x c - -fsyntax-only 2>&1 | grep '^ ' | cut -b2-)
PROCESSED=()
while read flag; do
if [ -n "${flag}" ]; then
INCLUDE_DIRS+=("${flag}")
fi
done < <(pkg-config --cflags-only-I "${PKG_CONFIG_PKGS}" | sed -E 's/-I([^ ]*)/\1\n/g')
function not_found {
echo " \"$1\" [style=filled,color=lightgrey];"
echo " \"$2\" -> \"$1\""
}
function handle_file {
filename="${1}"
for f in "${PROCESSED[#]}"; do
if [ "${f}" = "${1}" ]; then
echo " \"$2\" -> \"$1\""
return
fi
done
PROCESSED+=("$1")
if [ -n "${2}" ]; then
echo " \"${2}\" -> \"${1}\";"
fi
if [ ! "${FOLLOW_SYS_INCLUDES}" = "y" ]; then
for d in "${INCLUDE_DIRS[#]}"; do
case "${1}" in
"${d}"/*)
return
;;
esac
done
fi
parse_file "$1"
}
function handle_include {
case "${1}" in
/*)
handle_file "${name}" "$2"
return
;;
esac
for dir in "${INCLUDE_DIRS[#]}"; do
if [ -f "${dir}/${1}" ]; then
handle_file "${dir}/${1}" "$2"
return
fi
done
not_found "${1}" "${2}"
}
function handle_include_2 {
case "${1}" in
/*)
handle_file "${1}" "$2"
return
;;
esac
FILE="$(readlink -f "$(dirname "${2}")/${1}")"
if [ -f "${FILE}" ]; then
handle_file "${FILE}" "$2"
fi
}
function parse_file {
while read name; do
handle_include "$name" "$1";
done < <(grep '^[ \t]*#[ \t]*include <' "$1" | sed -E 's/[ \t]*#[ \t]*include ?<([^>]+)>.*/\1/')
while read name; do
handle_include_2 "$name" "$1" "$PWD";
done < <(grep '^[ \t]*#[ \t]*include "' "$1" | sed -E 's/[ \t]*#[ \t]*include \"([^"]+)\"/\1/')
}
echo "digraph G {"
echo "graph [rankdir = \"LR\"];"
parse_file "$(readlink -f "${1}")" "" "$PWD"
echo "}"
Pass it a file and it will generate a graphviz file. Pipe it to dot:
$ ./include-map.sh /usr/include/stdint.h | dot -Tx11
And you have something nice to look at.
Recently almost any of the featured IDE can help you in that built in.Visual Studio or Jetbrain's Clion are proper for that. You did not wrote neither platform or environment, but maybe it worth to give a try even with efforts to properly set the project that compiles.
Back in days I found also useful to generate documentation with doxygen, as I remember that will also create such maps /links/ as well.
Requirement :
Able to send a file containing numbers (433 434 435) as a parameter
sh Test.sh myFile.txt
Parameters can be numbers directly if not a file (433 434 434)
sh Test.sh 434 435 436
So , it has to support both file and numbers as the parameters
Below is the code i ve tried writing but in the for loop below , all numbers are getting printed as a string , but i need the for loop to run thrice as the input values are 3.
How to have it as a part of an array in shell script
Iam relatively new to shell script
OutPut:
In either case for loop has to run the number of parameter times(filedata determinies the parameters or direct input)
Please advice if any unforeseen bugs exist
#!/bin/bash
echo -e $# 2>&1 ;
myFile=$1 ; // As the first parameter will be a file
#[ -f "$myFile" ] && echo "$myFile Found" || echo "$myFile Not found"
if [ -f "$myFile" ]; then
tcId=`cat $#`;
echo $tcId;
else
tcId=$#;
echo $tcId;
fi
# Execute each of the given tests
for testCase in "$tcId"
do
echo "Test Case is "$testCase ;
done
I'd use a process substitution to "pretend" the explicit arguments are in a file.
while IFS= read -r testCase; do
echo "Test case is $testCase"
done < <( if [ -f "$1" ]; then
cat "$1"
else
printf "%s\n" "$#"
fi
)
If you are flexible in how your script is called, I would simplify it to only read test cases from standard input
while IFS= read -r testCase; do
echo "Test case is $testCase"
done
and call it one of two ways, neither using command line arguments:
sh Test.sh < myFile.txt
or
sh Test.sh <<TESTCASES
433
434
434
TESTCASES