ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
Since we are all stuck at home...good time to practice our bash, python, perl, php, C++ etc.
I'll start with one. Here is a very basic text only web browser that uses python3, tkinter, urllib, beautifulsoup, html2text, youtube_dl.
Everything is put into functions so that it's easy to find/understand. tkinter is about as easy as it gets to make a GUI with python3.
Go ahead and pick it apart, put some forward/back buttons on it, make the utube functionality better, use tkinter.ttk instead of tkinter to colorize it, use a grid instead of frames...
It shows how to make elements with tkinter, get input from them, display output in them, save the element content to file.
Code:
#! /usr/bin/python
from tkinter import *
from urllib import request
from bs4 import BeautifulSoup
from html2text import html2text, HTML2Text
from youtube_dl import YoutubeDL
#User agent for requests
'''
agent = ('Mozilla/5.0 (Windows NT 10.1; x86_64; rv:75.0)'
' Gecko/20100101 Firefox/75.0')
'''
agent = ('Mozilla/5.0')
#Make request header
user_agent = {'User-Agent': agent,
'Accept': 'text/html,application/xhtml+xml,'
'application/xml;q=0.9,*/*;q=0.8',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
'Accept-Encoding': 'none',
'Accept-Language': 'en-US,en;q=0.8',
'Connection': 'keep-alive'}
#Home page
#homePage = 'file:///path/to/Bookmarks.html'
homePage = 'https://en.wikipedia.org/wiki/Carbon'
#homePage = 'https://www.youtube.com/watch?v=5Q5g-krgDnk'
def TkBrowser():
#Load the page with urllib
def loadPage():
req = request.Request(entry.get(),
data=None, headers=user_agent)
page = request.urlopen(req)
return page
#Get page source
def viewSource():
text.delete(1.0, END)
pSource = loadPage()
html = pSource.read()
text.insert(1.0, html)
return html
#Write source code to file
def saveSource():
getHtml = viewSource().splitlines()
sSource = '\n'.join(line.decode('utf-8') for line in getHtml)
with open('TkBSource.html', 'a') as f:
f.write(sSource)
#Get text of page with soup
def textBrowse():
text.delete(1.0, END)
get = loadPage()
soup = BeautifulSoup(get, features='lxml')
#Kill all script and style elements
for s in soup(["script", "style"]):
s.extract()
txt = '\n'.join(soup.get_text().splitlines())
text.insert(1.0, txt)
return txt
#Get text of page with html2text
def textBrowse2():
text.delete(1.0, END)
pSource = loadPage()
html = pSource.read()
noLinks = HTML2Text()
noLinks.ignore_links = True
txt = noLinks.handle(html.decode('utf-8'))
text.insert(1.0, txt)
#Save text of page to file
def savePage():
getPage = textBrowse()
with open('TkBText.txt', 'w') as f:
f.write(getPage)
#Get links on page with soup
def pageLinks():
text.delete(1.0, END)
getP = loadPage()
soup = BeautifulSoup(getP, 'lxml')
hrefTags = soup.find_all(href=True)
links = [tag.get('href') for tag in hrefTags]
linkList = '\n'.join(l for l in links if l)
text.insert(1.0, linkList)
return linkList
#Save links to file
def saveLinks():
getLinks = pageLinks()
with open('TkBLinks.txt', 'w') as f:
f.write(getLinks)
#Get Utube video urls
def getUtube():
text.delete(1.0, END)
ulist = []
yt = YoutubeDL()
info = yt.extract_info(entry.get(), download=False)
ulist.append(info['title'])
ulist.append('')
ulist.append(info['description'])
ulist.append('')
try:
for i in info['formats']:
print(i)
ulist.append(i['format_id'])
ulist.append(i['url'])
ulist.append('')
utList = '\n'.join(ulist)
except:
pass
text.insert(1.0, utList)
return utList
#Save Utube url's to file
def saveUtube():
getTube = getUtube()
with open('TkBUtube.txt', 'w') as f:
f.write(getTube)
#Make a window
browser = Tk()
browser.title('PyTk Browser')
#Window size
browser.geometry('1200x900')
fontSize = ('Arial', 18)
bg='grey20'
fg='white'
bw=3
#Make Frames
top = Frame(browser)
bottom = Frame(browser)
top.pack(side=TOP)
bottom.pack(side=BOTTOM, fill=BOTH, expand=True)
#Make menubar
menubar = Menu(browser)
browser.config(menu=menubar, bg=bg)
menubar.config(font=fontSize, bd=bw, bg=bg, fg=fg)
#Make menu entries
#Load menu
loadmenu = Menu(menubar, tearoff=0)
loadmenu.config(
font=fontSize, bd=bw, bg=bg, fg=fg)
loadmenu.add_command(
label="Get page text with Beautiful Soup", command=textBrowse)
loadmenu.add_command(
label="Get page text with Html2text", command=textBrowse2)
loadmenu.add_command(
label="Save page text to file", command=savePage)
#Source menu
sourcemenu = Menu(menubar, tearoff=0)
sourcemenu.config(
font=fontSize, bd=bw, bg=bg, fg=fg)
sourcemenu.add_command(
label="View page source code", command=viewSource)
sourcemenu.add_command(
label="Save page source code to file", command=saveSource)
#Links menu
linksmenu = Menu(menubar, tearoff=0)
linksmenu.config(
font=fontSize, bd=bw, bg=bg, fg=fg)
linksmenu.add_command(
label="View page links", command=pageLinks)
linksmenu.add_command(
label="Save page links to file", command=saveLinks)
#UTube menu
utubemenu = Menu(menubar, tearoff=0)
utubemenu.config(
font=fontSize, bd=bw, bg=bg, fg=fg)
utubemenu.add_command(
label="View utube url's", command=getUtube)
utubemenu.add_command(
label="Save utube url's to file", command=saveUtube)
#Make a url entry widget
entry = Entry(browser)
label = Label(text='url')
label.config(font=fontSize, bg=bg, fg=fg, highlightthickness=3)
entry.config(font=fontSize, width=80, bd=bw,
highlightcolor='blue', highlightthickness=3)
#Bind enter key to textBrowse()
entry.bind('<Return>', lambda event=None: textBrowse())
entry.insert(END, homePage)
#Make a scroll bar
scroll = Scrollbar(browser)
scroll.config(width=25, bd=bw, bg=bg, troughcolor=fg)
#Make text widget
text = Text(browser, yscrollcommand=scroll.set)
text.config(font=fontSize, bd=bw, bg='white', fg='black',
highlightcolor='blue', highlightthickness=3)
scroll.config(command=text.yview)
#Place widgets
menubar.add_cascade(label="Load", menu=loadmenu)
menubar.add_cascade(label="Source", menu=sourcemenu)
menubar.add_cascade(label="Links", menu=linksmenu)
menubar.add_cascade(label="UTube", menu=utubemenu)
entry.pack(in_=top, side=LEFT)
label.pack(in_=top, side=RIGHT)
scroll.pack(in_=bottom, side=RIGHT, fill=Y)
text.pack(in_=bottom, side=TOP, fill="both", expand=True)
browser.mainloop()
if __name__ == "__main__":
TkBrowser()
HTML-Tidy in its current version 5.7.28 does not appear to support the option --tidy-mark, although it remains in the man-page.
The “problem” (what a word) I have with that, comes from the fact that a “generator” meta-tag appears to be horribly exaggerated for all the HTML I do, these days. I already use to add a comment right below the XML-declaration: “Created with nothing”. Now, the fact that Tidy itself insists in, -and cannot be persuaded otherwise-, the addition of its own generator tag provokes me to add the VI-editor in that line:
Code:
<meta name="generator" content="vi - Vi Improved, a programmer's editor; HTML Tidy for HTML5 for Linux version 5.7.28" />
Tidy coming back later for another check on the same HTML-file will not recognize this meta-tag and adds yet another one! Irrespective of the --tidy-mark option being used or not.
So here is my solution. It is complex, it is completely useless and ... I wrote that yesterday. Do not bare with me too much, if there are obvious coding errors, but neither make a fuss about it...
This is not about XML. In deed, I started off parsing the page with nokogiri, before I woke up... Maybe that is the whole point of this post.., or maybe you find another.
^ Nice!
Wouldn't it be better to use Xorg's rgb.txt directly?
I just looked, it's built straight into the server executable nowadays, what a bummer.
But e.g. xplanet still ships a text version: /usr/share/xplanet/rgb.txt
Can tkinter do tooltips? Then you could avoid the problem with dark/light font.
Kid game. Enter the colors before the clock runs out. The timer will run faster if you enter a wrong answer. The answer is given to them at first, if they figure that out. Age range 3-8 I would say. Teaches colors and typing. Modify it how you wish.
Code:
#!/usr/bin/python
#Enter the color you see game.
from tkinter import *
from random import shuffle
color = ['Red','Orange','Yellow','Blue','Green','Pink',
'White','Purple','Brown','Gray', 'Maroon', 'Cyan',
'Tan', 'Violet']
class colorGame():
def __init__(self, score, timeleft, hurry):
super(colorGame, self).__init__()
self.score = score
self.timeleft = timeleft
self.hurry = hurry
def start(event):
if self.timeleft > 0:
colors()
if self.score == score:
count()
def colors():
if self.timeleft > 0:
if ent.get().lower() == color[1].lower():
self.score += 1
ent.delete(0, END)
shuffle(color)
colorLabel.config(fg=str(color[1]),
text=str(color[0]))
scoreLabel.config(text="\nScore: "
+ str(self.score))
def count():
t=[]
for c in color:
t.append(c)
hurryLabel.config(text='\nColors are ' + str(t),
font=fontSize3)
if self.timeleft > 0:
self.timeleft -= 1
timeLabel.config(text="\nTime left: "
+ str(self.timeleft)+'\n', font=fontSize)
timeLabel.after('1000', count)
if self.timeleft in range(1, 30):
hurryLabel.config(text="\nHurry!", font=fontSize)
if self.timeleft == 0:
hurryLabel.config(text="\nDone! "
+ "Can you get a better score?", font=fontSize)
bg = 'black'
fg = 'white'
root = Tk()
root.config(bg=bg)
root.title("Enter The Color Game")
root.geometry("1400x700")
fontSize = ('Monospace', 34)
fontSize2 = ('Monospace', 80)
fontSize3 = ('Monospace', 12)
inst = Label(root,
text="Enter color of words before the clock runs out",
font=fontSize, bg=bg, fg=fg)
inst.pack()
scoreLabel = Label(root,
text="Press Enter to start", font=fontSize, bg=bg, fg=fg)
scoreLabel.pack()
timeLabel = Label(root, text = "\nTime left: " +
str(timeleft), font=fontSize, bg=bg, fg=fg)
timeLabel.pack()
colorLabel = Label(root, font=fontSize2, bg=bg, fg=fg)
colorLabel.pack()
ent = Entry(root)
ent.config(font=fontSize)
ent.pack()
ent.focus_set()
hurryLabel = Label(root, font=fontSize, bg=bg, fg=fg)
hurryLabel.pack()
root.bind('<Return>', start)
root.mainloop()
if __name__ == "__main__":
score = 0
#Set clock times here
timeleft = 60
hurry = 30
colorGame(score, timeleft, hurry)
Half of the pride comes from the fact that I found the name “busy-indicator” for my Ruby-Class right before I learned that this is what you usually call such a routine or object... also in different languages than Ruby.
So this serves to show activity while a program does something “in the background”. The whole code is in an archive that I link here: http://www.uplawski.eu/busy_indicato...dicator.tar.xz
A GnuPG-signature for the archive is attached to this post (key RSA C36288CD0571D71B0D7642A1688FE966708A032E).
I show you here the principle class “BusyIndicator” and continue my palaver below:
The test-code at the bottom is executed when you call the Ruby-Interpreter with this file as argument, i.e. when you do as if it were an executable and provided, the file “color_output.rb” is present, too.
The archive contains in the sub-directory “bin” an executable Ruby-program which serves as example-code and demonstrator. It can also be called in shell-scripts... I think of something like this:
You can call “busy” without argument to see a default text-animation. Otherwise, the argument is a list of symbols which will be at display while the program runs, each one for 0.1 seconds.
You must hit the key 'q' to end the busy-program, other keys are ignored
Here is the code of the example-program “busy”:
Code:
#!/bin/env ruby
require_relative "../busy_indicator"
require_relative "../user_input"
bi = BusyIndicator.new(false, 4)
if !ARGV.empty?
bi.sequence=ARGV[0]
end
bi.run
while true
if wait_for_user().chr == 'q'
bi.stop("")
exit true
end
end
Last edited by Michael Uplawski; 04-26-2020 at 12:22 PM.
Reason: Nicer example sequence
In another thread a few here helped me get a download script sorted out. Happy about that. Another I've been fiddling with over time is my backup script. It's gone through several rewrites and minor changes. I'm fairly happy with it now.
Code:
for user in /home/* ; do
BACKUPUSER=$(basename "$user")
TARGETDIR="$POOLLOC"/backups/homebackups/"$HOSTNAME"/"$BACKUPUSER"
if [[ ! -d "$TARGETDIR" ]] ; then
mkdir -p "$TARGETDIR"
chown -R "$BACKUPUSER":"$BACKUPUSER" "$TARGETDIR"
fi
su -c "duplicity \
--exclude-if-present .nobackup \
--no-encryption \
--full-if-older-than 1M \
su -c \
"duplicity remove-all-but-n-full 4 --force file://${TARGETDIR}" \
"$BACKUPUSER"
done
Beginnings of a paint/draw program.
Needs python-pillow, python-pyscreenshot for saving.
Or #from pyscreenshot import * to use without. You could use a global in getXY and addLine instead of appending xy to list. I wanted to see if I could make a scrolling list. You could also return something from those functions and share variables with the class.
Code:
#!/usr/bin/python
#Draw with mouse, save to file
from tkinter import *
from pyscreenshot import *
class Paint():
def __init__(self):
Lxy = []
def getXY(self):
Lx = self.x
Ly = self.y
Lxy.append(Lx)
Lxy.append(Ly)
def addLine(self):
getXY(self)
canvas.create_line((Lxy[0], Lxy[1], Lxy[2], Lxy[3]))
del Lxy[:-2]
def clear(self):
canvas.delete("all")
def save(self):
x=root.winfo_rootx()+canvas.winfo_x()
y=root.winfo_rooty()+canvas.winfo_y()
x1=x+canvas.winfo_width()
y1=y+canvas.winfo_height()
im = grab((x, y, x1, y1))
im.save("TkDraw.png")
root = Tk()
root.geometry("800x600")
root.title('TkDraw')
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
canvas = Canvas(root)
canvas.config(bg='white')
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
#Left button draw
canvas.bind("<Button-1>", getXY)
canvas.bind("<B1-Motion>", addLine)
#Right click on 3 button mouse, clear canvas
canvas.bind("<Button-3>", clear)
#Control s save to file
root.bind("<Control-s>", save)
root.mainloop()
if __name__ == '__main__':
Paint()
I've been teaching myself C and, with all this time at home, have rewritten most of my bash scripts in C. It's been a journey, but I see why so many people like the language: It's fast, gives you all control, and extremely portable.
In the last few days since I've been playing more games on Windows lately I've added an Xserver to my installation via Xming, set up an lxd container on my server just for ssh tunneling. I use Kate for my scripts mainly and can now use it on Windows. I just finished hacking together a script for backing up Windows user directories via sharing directories and mounting them with systemd autmount in /etc/fstab. This script runs at 3 am and also backs up the big download part of World of Warcraft. I need to find a better way of getting the mac addresses but this seems to work, and it's not run often enough to be a problem in regards to using available muscle.
Code:
for windowsbox in "$@" ; do
if ! ping -c 1 "$windowsbox".mylan.home ; then
WINMACADDRESS=$(ssh jason@services.mylan.home grep -A 1 "$windowsbox" \
/etc/dhcp/ltsp.dhcpd.conf | grep ethernet | awk '{print $3}' \
| cut -d \; -f 1)
wakeonlan "$WINMACADDRESS"
sleep 180
fi
WINMOUNT=/mnt/windows/"$windowsbox"
BACKUPDIR="$POOLLOC"/backups/homebackups/"$windowsbox".windows
if [[ -d "$WINMOUNT"/wow ]] ; then
[[ -d "$BACKUPDIR"/wow ]] || mkdir -p "$BACKUPDIR"/wow/
for folder in Data _retail_ ; do
rsync -auv "$WINMOUNT"/wow/"$folder" "$BACKUPDIR"/wow/
done
fi
if [[ -d "$WINMOUNT"/users/Default ]] ; then
for folder in $(find "$WINMOUNT"/users/ -maxdepth 1 -mindepth 1 -type d) \
; do
FOLDER=$(basename "$folder")
if [[ "$FOLDER" != Default ]] && [[ "$FOLDER" != desktop.ini ]] ; then
# this case is only because i haven't bothered changing my windows username.
case "$FOLDER" in
jmgib)
SERVERUSER=jason
;;
esac
if [[ ! -d "$BACKUPDIR"/users/"$FOLDER" ]] ; then
echo "${BACKUPDIR}/users/${FOLDER}"
mkdir -p "$BACKUPDIR"/users/"$FOLDER"
chown "$SERVERUSER":"$SERVERUSER" "$BACKUPDIR"/users/"$FOLDER"
fi
su -c "rsync -auv --exclude={'OneDrive','MicrosoftEdgeBackups'} \
${folder} ${BACKUPDIR}/users/" "$SERVERUSER"
fi
done
fi
done
Google Chrome has an irritating habit of asking for a password to unlock login keyring. This happens every time there is an update to google chrome. (I don't know if this issue applies to other Linux distributions. I am using Mint) I used to fix manually, it's a quick and easy manual fix; but I got tired of doing that so I wrote the script below. It was a very good learning experience.
Code:
#!/bin/bash
#
# Google Chrome Asks Password to Unlock Login Keyring
# This script must be run using sudo. "sudo bash fix_chrome.sh"
# https://tipsonubuntu.com/2017/12/20/...login-keyring/
# https://www.cyberciti.biz/faq/how-to...ux-unix-shell/
# April 28, 2020
#
chrome_dir="/usr/share/applications"
chrome_filename="google-chrome.desktop"
# Check how to use regex in Linux
# ^ needed to limit search to string at begging of line.
# $ End of line anchor
# . Any one intervening characters
# Note the %U in the string "Exec=/usr/bin/google-chrome-stable %U". Does not appear to be normal ASCII characters
# Substituting ... for the space plus %U
# The / need to be escaped with \ result -> \/
# Exec=/usr/bin/google-chrome-stable --password-store=basic %U
string_to_find='^Exec=/usr/bin/google-chrome-stable...$'
string_to_insert='password-store=basic'
string_new='Exec=\/usr\/bin\/google-chrome-stable --password-store=basic %U'
#
# Move to the Directory the Chrome file is located in.
cd ${chrome_dir}
# Check if file exists
if [ -f ${chrome_filename} ]
then
printf "\nFile Exists"
else
printf "\nFile Does NOT Exist. Is Chrome installed? Program terminated\n"
exit 1
fi
# Verify whether google-chrome.desktop has or has not been already inserted
if grep -qw ${string_to_insert} ${chrome_filename}
then
printf "\n${string_to_insert} string found. Nothing needs to be done. Already fixed.\n"
exit 1
fi
# Check if there is a unique occurance of the string-to-find.
num_found=$(grep -c ${string_to_find} ${chrome_filename})
if [ $num_found -eq 1 ]
then
printf "\nUnique occurance found.\n"
line_number=$(grep -n ${string_to_find} ${chrome_filename} | cut -f1 -d: )
printf "Line Number: ${line_number}\n"
else
printf "\nFAILED. A total of ${num_found} occurance were found. Must have only one unique occurance. Program terminated.\n"
exit 1
fi
# Make Modification to file
sed -i "${line_number}s/.*/${string_new}/" "${chrome_filename}"
if [ $? -eq 0 ]
then
echo "Success. Correction Made."
else
echo "FAILED to update"
fi
Hopefully others may find this quick fix useful.
PS: Purposely avoided the use of nested if statements.
I'd recommend you StackOverFlow. Seems like this forum knows every single answer to programming questions. I also started to do some coding during quarantine. And if I don't know or understand something, I go there. Sometimes even GitHub has some part of code that you need. Hope this will help you in the future.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.