linux: Access a directory containing a space - bash

In a script, I have this line
#!/bin/sh
log="${var}logs/console logs/since2_%m-%d-%Y.log" # <-- console logs has a space
how can I access this file?
putting quotes like:
log="${var}logs/"console logs"/since2_%m-%d-%Y.log"
cancels out the quotes around it, and escaping the quotes makes it try to find a file containing the character "

The trouble you're having is probably where you use $log, you should probably be using "$log" to preserve the spaces.

The problem is not what is quoted in the question. Here is an example script which works. Note the quotes around the USAGE of $log in addition to the definition. If you want further help, post the complete script or a minimal working subset which people can run to reproduce the problem.
#!/bin/sh
var=/tmp/
log="${var}logs/console logs/since2_%m-%d-%Y.log"
mkdir -p "$log"
rmdir "$log"
fortune | tee "$log"
echo ----
cat "$log"

the variable $IFS holds the field separator, which by default is space, so try with
oldifs="$IFS"
IFS="
"
log="${var}logs/console logs/since2_%m-%d-%Y.log"
# do whatever you want with $log now
IFS=$oldifs

If you intend to have today's date in that filename:
log="$(date "+${var}logs/console logs/since2_%m-%d-%Y.log")"
touch "$log"
I'd recommend you use %Y-%m-%d as that sorts both cronologically and lexically.

I think log="${var}logs/console\ logs/since2_%m-%d-%Y.log" should work. Try once
The idea is to escape the [SPACE]

Related

How do I use `sed` to alter a variable in a bash script?

I'm trying to use enscript to print PDFs from Mutt, and hitting character encoding issues. One way around them seems to be to just use sed to replace the problem characters: sed -ir 's/[“”]/"/g' {input}
My test input file is this:
“very dirty”
we’re
I'm hoping to get "very dirty" and we're but instead I'm still getting
â\200\234very dirtyâ\200\235
weâ\200\231re
I found a nice little post on printing to PDFs from Mutt that I used as a starting point. I have a bash script that I point to from my .muttrc with set print_command="$HOME/.mutt/print.sh" -- the script currently reads about like this:
#!/bin/bash
input="$1" pdir="$HOME/Desktop" open_pdf=evince
# Straighten out curly quotes
sed -ir 's/[“”]/"/g' $input
sed -ir "s/[’]/'/g" $input
tmpfile="`mktemp $pdir/mutt_XXXXXXXX.pdf`"
enscript --font=Courier8 $input -2r --word-wrap --fancy-header=mutt -p - 2>/dev/null | ps2pdf - $tmpfile
$open_pdf $tmpfile >/dev/null 2>&1 &
sleep 1
rm $tmpfile
It does a fine job of creating a PDF (and works fine if you give it a file as an argument) but I can't figure out how to fix the curly quotes.
I've tried a bunch of variations on the sed line:
input=sed -r 's/[“”]/"/g' $input
$input=sed -ir "s/[’]/'/g" $input
Per the suggestion at Can I use sed to manipulate a variable in bash? I also tried input=$(sed -r 's/[“”]/"/g' <<< $input) and I get an error: "Syntax error: redirection unexpected"
But none manages to actually change $input -- what is the correct syntax to change $input with sed?
Note: I accepted an answer that resolved the question I asked, but as you can see from the comments there are a couple of other issues here. enscript is taking in a whole file as a variable, not just the text of the file. So trying to tweak the text inside the file is going to take a few extra steps. I'm still learning.
On Editing Variables In General
BashFAQ #21 is a comprehensive reference on performing search-and-replace operations in bash, including within variables, and is thus recommended reading. On this particular case:
Use the shell's native string manipulation instead; this is far higher performance than forking off a subshell, launching an external process inside it, and reading that external process's output. BashFAQ #100 covers this topic in detail, and is well worth reading.
Depending on your version of bash and configured locale, it might be possible to use a bracket expression (ie. [“”], as your original code did). However, the most portable thing is to treat “ and ” separately, which will work even without multi-byte character support available.
input='“hello ’cruel’ world”'
input=${input//'“'/'"'}
input=${input//'”'/'"'}
input=${input//'’'/"'"}
printf '%s\n' "$input"
...correctly outputs:
"hello 'cruel' world"
On Using sed
To provide a literal answer -- you almost had a working sed-based approach in your question.
input=$(sed -r 's/[“”]/"/g' <<<"$input")
...adds the missing syntactic double quotes around the parameter expansion of $input, ensuring that it's treated as a single token regardless of how it might be string-split or glob-expanded.
But All That May Not Help...
The below is mentioned because your test script is manipulating content passed on the command line; if that's not the case in production, you can probably disregard the below.
If your script is invoked as ./yourscript “hello * ’cruel’ * world”, then information about exactly what the user entered is lost before the script is started, and nothing you can do here will fix that.
This is because $1, in that scenario, will only contain “hello; ’cruel’ and world” are in their own argv locations, and the *s will have been replaced with lists of files in the current directory (each such file substituted as a separate argument) before the script was even started. Because the shell responsible for parsing the user's command line (which is not the same shell running your script!) did not recognize the quotes as valid at the time when it ran this parsing, by the time the script is running, there's nothing you can do to recover the original data.
Abstract: The way to use sed to change a variable is explored, but what you really need is a way to use and edit a file. It is covered ahead.
Sed
The (two) sed line(s) could be solved with this (note that -i is not used, it is not a file but a value):
input='“very dirty”
we’re'
sed 's/[“”]/\"/g;s/’/'\''/g' <<<"$input"
But it should be faster (for small strings) to use the internals of the shell:
input='“very dirty”
we’re'
input=${input//[“”]/\"}
input=${input//[’]/\'}
printf '%s\n' "$input"
$1
But there is an underlying problem with your script, you are trying to clean an input received from the command line. You are using $1 as the source of the string. Once somebody writes:
./script “very dirty”
we’re
That input is lost. It is broken into shell's tokens and "$1" will be “very only.
But I do not believe that is what you really have.
file
However, you are also saying that the input comes from a file. If that is the case, then read it in with:
input="$(<infile)" # not $1
sed 's/[“”]/\"/g;s/’/'\''/g' <<<"$input"
Or, if you don't mind to edit (change) the file, do this instead:
sed -i 's/[“”]/\"/g;s/’/'\''/g' infile
input="$(<infile)"
Or, if you are clear and certain that what is being given to the script is a filename, like:
./script infile
You can use:
infile="$1"
sed -i 's/[“”]/\"/g;s/’/'\''/g' "$infile"
input="$(<"$infile")"
Other comments:
Then:
Quote your variables.
Do not use the very old `…` syntax, use $(…) instead.
Do not use variables in UPPER case, those are reserved for environment variables.
And (unless you actually meant sh) use a shebang (first line) that targets bash.
The command enscript most definitively requires a file, not a variable.
Maybe you should use evince to open the PS file, there is no need of the step to make a pdf, unless you know you really need it.
I believe that is better use a file to store the output of enscript and ps2pdf.
Do not hide the errors printed by the commands until everything is working as desired, then, just call the script as:
./script infile 2>/dev/null
Or as required to make it less verbose.
Final script.
If you call the script with the name of the file that enscript is going to use, something like:
./script infile
Then, the whole script will look like this (runs both in bash or sh):
#!/usr/bin/env bash
Usage(){ echo "$0; This script require a source file"; exit 1; }
[ $# -lt 1 ] && Usage
[ ! -e $1 ] && Usage
infile="$1"
pdir="$HOME/Desktop"
open_pdf=evince
# Straighten out curly quotes
sed -i 's/[“”]/\"/g;s/’/'\''/g' "$infile"
tmpfile="$(mktemp "$pdir"/mutt_XXXXXXXX.pdf)"
outfile="${tmpfile%.*}.ps"
enscript --font=Courier10 "$infile" -2r \
--word-wrap --fancy-header=mutt -p "$outfile"
ps2pdf "$outfile" "$tmpfile"
"$open_pdf" "$tmpfile" >/dev/null 2>&1 &
sleep 5
rm "$tmpfile" "$outfile"

echo: "Why need to use \\ instead of \ to cancel the / character?"

~/Desktop $ echo */*
unix/junk unix/save unix/xyxxy
I would like to cancel the slash so the shell no longer prints the files of the directories. I've found out that
~/Desktop $ echo *\/*
unix/junk unix/save unix/xyxxy
doesn't work
,while
~/Desktop $ echo *\\/*
*\/*
does the job.
Can someone explain why this is happening?
Actually, what you're trying to cancel here is not the special behavior of the \ character, but rather, the special behavior of the two * characters!
Me, I would use
echo '*/*'
or
echo "*/*"
Or, if you wanted to use \, the best way to do it would be
echo \*/\*
Actually, since there are almost certainly no files or directories named "*", you would usually be able to get away with just
echo \*/*
or
echo */\*
When you wrote
echo *\\/*
you were asking to see all the names of all the files in any subdirectory where the subdirectory name ended in a \. There probably aren't any of those, but if you want to, to see what's going on, try invoking
mkdir x\\
touch x\\/y
touch x\\/z
and then do your
echo *\\/*
again. You should see x\/y x\/z!
A single backslash is used for special sequences like for instance \n . So to insert a real - escaping - backslash, you have to write \\.
If all you want is to print the folders within your Desktop then use echo */
echo *\\/* will print anything withing a folder that ends with \
while echo *\/* will print anything withing any folder as the backslash (\) is not scaped
I didn't make myself clear with the earlier answer. What's happening with the last command
~/Desktop $ echo *\\/*
*\/*
is basically \ is escaping the next \ and *\/* is just getting printed like any other ordinary string like it happens with *something or any other string abc

Batch rename files using mv and sed within a for loop results in exit code 64 - How do I correct the script?

I am attempting to rename several hundred folders to remove numbers that are prefixed to the folder name. I seem to have gotten most of the way there, but my script does not quite work yet. When I echo the commands created by the script instead of running those commands, everything looks fine. The script:
for file in *
do
echo mv "'"$file"'" $(echo "'"$file"'" | sed 's/[0-9]\{1,3\} //')
done
returns many lines of:
mv '123 filename' 'filename'
mv '99 another name' 'another name'
. . . etc.
If I take one of those lines and use on the command line, the folder is renamed appropriately. If I remove the echo to actually run those mv commands, though, they do not work. Instead, mv prints out the usage reminder that comes when you incorrectly enter the command in some way.
Why do the output commands work individually but not within the for loop? How can I correct this script?
Your quoting looks oddball. The variable should be in double quotes, no more and no less.
Furthermore, you should avoid the brittle $(echo ... | sed ...); this can be accomplished in pure shell:
for f in *; do
g=${f#[0-9]}; g=${g#[0-9]}; g=${g#[0-9]}
echo mv "$f" "${g# }"
done
Run with sh -x if you want to verify that the echo gets correct quoting. Then remove the echo to actually move.
The reason the output worked when copy/pasted is that the second level of quoting was actually necessary when copying the output from echo. A (risky, convoluted) workaround would have been to replace echo with eval.

How to pass a variable containing space in bash as a loop argument?

good day,
I am creating a script to read one level of subfolders/directories of a path. The script is like so:
#loopdir.sh
for i in `ls -d $1`
do
echo $i
done
But when I tried to use it to read /media/My\ Passport/, it reads the argument as two different dirs:
$ ./loopdir.sh /media/My\ Passport/
ls: cannot access /media/My: No such file or directory
Passport/
Try doing this instead (my understanding is that you want to list subdirs, Am I right?) :
for i in "$1"/*; do
echo "${i%/}"
done
Parsing ls output is a bad idea : it's is a tool for interactively looking at file information. Its output is formatted for humans and will cause bugs in scripts. Use globs or find instead. Understand why: http://mywiki.wooledge.org/ParsingLs
And (last but not least) : USE MORE QUOTES! They are vital. Also, learn the difference between ' and " and `. See http://mywiki.wooledge.org/Quotes and http://wiki.bash-hackers.org/syntax/words
You need to surround your $i with quotes: echo "$i"
don't $i
this will break by space
using "$i"

Problems with shell scriptings using Sed

Me and a friend are working on a project, and We have to create a script that can go into a file, and replace all occurances of a certain expression/word/letter with another using Sed. It is designed to go through multiple tests replacing all these occurances, and we don't know what they will be so we have to anticipate anything. We are having trouble on a certain test where we need to replace 'l*' with 'L' in different files using a loop. The code that i have is
#!/bin/sh
p1="$1"
shift
p2="$1"
shift
for file in "$#" #for any file in the directory
do
# A="$1"
#echo $A
#B="$2"
echo "$p1" | sed -e 's/\([*.[^$]\)/\\\1/g' > temporary #treat all special characters as plain text
A="`cat 'temporary'`"
rm temporary
echo "$p1"
echo "$file"
sed "s/$p1/$p2/g" "$file" > myFile.txt.updated #replace occurances
mv myFile.txt.updated "$file"
cat "$file"
done
I have tried testing this on practice files that contain different words and also 'l*' But whenever i test it, it deletes all the text in the file. Can someone help me with this, we would like to get it done soon. Thanks
It looks like you are trying to set A to a version of p1 with all special characters escaped. But you use p1 later instead of A. Try using the variable A, and also try setting it without a temporary file:
A=$( echo "$p1" | sed -e 's/\([*.[^$]\)/\\\1/g' )

Resources